Skip to main content

aiproof_config/
lib.rs

1//! aiproof-config: Configuration loader with cascade support.
2
3pub mod config;
4pub mod error;
5
6pub use config::Config;
7pub use error::ConfigError;
8
9use std::path::Path;
10
11/// Cascade loader: checks for config files in order:
12/// 1. `.aiproofrc` in `start` or any ancestor — standalone TOML, deserialized as `Config`.
13/// 2. `pyproject.toml` in `start` or any ancestor — look for `[tool.aiproof]` table.
14/// 3. `Config::default()`.
15///
16/// First file that matches (by walking upward from `start`) wins. We don't merge.
17pub fn load(start: &Path) -> Result<Config, ConfigError> {
18    for ancestor in start.ancestors() {
19        let aiproofrc = ancestor.join(".aiproofrc");
20        if aiproofrc.is_file() {
21            return load_aiproofrc(&aiproofrc);
22        }
23        let pyproject = ancestor.join("pyproject.toml");
24        if pyproject.is_file()
25            && let Some(cfg) = try_pyproject(&pyproject)?
26        {
27            return Ok(cfg);
28        }
29    }
30    Ok(Config::default())
31}
32
33fn load_aiproofrc(path: &Path) -> Result<Config, ConfigError> {
34    let text = std::fs::read_to_string(path).map_err(|source| ConfigError::Io {
35        path: path.display().to_string(),
36        source,
37    })?;
38    toml::from_str::<Config>(&text).map_err(|e| ConfigError::Toml {
39        path: path.display().to_string(),
40        message: e.to_string(),
41    })
42}
43
44fn try_pyproject(path: &Path) -> Result<Option<Config>, ConfigError> {
45    let text = std::fs::read_to_string(path).map_err(|source| ConfigError::Io {
46        path: path.display().to_string(),
47        source,
48    })?;
49    let value: toml::Value = text
50        .parse()
51        .map_err(|e: toml::de::Error| ConfigError::Toml {
52            path: path.display().to_string(),
53            message: e.to_string(),
54        })?;
55    let Some(table) = value.get("tool").and_then(|t| t.get("aiproof")) else {
56        return Ok(None);
57    };
58    let serialized = toml::to_string_pretty(table).map_err(|e| ConfigError::Toml {
59        path: path.display().to_string(),
60        message: e.to_string(),
61    })?;
62    toml::from_str::<Config>(&serialized)
63        .map(Some)
64        .map_err(|e| ConfigError::Toml {
65            path: path.display().to_string(),
66            message: format!("in [tool.aiproof]: {e}"),
67        })
68}