agent-first-psql 0.6.2

A PostgreSQL interface for AI agents: reliable, structured, explicit, and read-only by default.
Documentation
use std::path::Path;

pub fn test_dsn() -> Option<String> {
    env_value("AFPSQL_TEST_DSN_SECRET").or_else(|| env_value("DATABASE_URL"))
}

#[allow(dead_code)]
pub fn required_test_dsn() -> String {
    let dsn = test_dsn();
    assert!(
        dsn.is_some(),
        "set AFPSQL_TEST_DSN_SECRET or DATABASE_URL, or create tests/.env.local for PostgreSQL integration tests",
    );
    dsn.unwrap_or_default()
}

pub fn env_value(key: &str) -> Option<String> {
    std::env::var(key).ok().or_else(|| read_env_key(key))
}

fn read_env_key(key: &str) -> Option<String> {
    for path in [env_file_path(".env.local"), env_file_path(".env")] {
        if let Some(value) = read_env_key_from_file(&path, key) {
            return Some(value);
        }
    }
    None
}

fn env_file_path(name: &str) -> std::path::PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join(name)
}

fn read_env_key_from_file(path: &Path, key: &str) -> Option<String> {
    let text = std::fs::read_to_string(path).ok()?;
    for line in text.lines() {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let line = line.strip_prefix("export ").unwrap_or(line);
        let Some((name, value)) = line.split_once('=') else {
            continue;
        };
        if name.trim() == key {
            return Some(unquote_env_value(value.trim()));
        }
    }
    None
}

fn unquote_env_value(value: &str) -> String {
    if value.len() >= 2 {
        let bytes = value.as_bytes();
        if (bytes[0] == b'"' && bytes[value.len() - 1] == b'"')
            || (bytes[0] == b'\'' && bytes[value.len() - 1] == b'\'')
        {
            return value[1..value.len() - 1].to_string();
        }
    }
    value.to_string()
}