clapfig 0.9.2

Rich, layered configuration for Rust CLI apps
Documentation
use std::path::PathBuf;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ClapfigError {
    #[error("Unknown key '{key}' in {path} (line {line})")]
    UnknownKey {
        key: String,
        path: PathBuf,
        line: usize,
    },

    #[error("Unknown keys in config file:\n{}", .0.iter().map(|e| format!("  - {e}")).collect::<Vec<_>>().join("\n"))]
    UnknownKeys(Vec<ClapfigError>),

    #[error("Failed to parse {path}: {source}")]
    ParseError {
        path: PathBuf,
        source: toml::de::Error,
    },

    #[error("Failed to read {path}: {source}")]
    IoError {
        path: PathBuf,
        source: std::io::Error,
    },

    #[error("Configuration error: {0}")]
    ConfigError(#[from] confique::Error),

    #[error("Key not found: {0}")]
    KeyNotFound(String),

    #[error("Invalid value for '{key}': {reason}")]
    InvalidValue { key: String, reason: String },

    #[error("No persist scopes configured — call .persist_scope() on the builder")]
    NoPersistPath,

    #[error("Ancestors is not valid as a persist scope path (it resolves to multiple directories)")]
    AncestorsNotAllowedAsPersistPath,

    #[error("Unknown scope '{scope}' — available scopes: {}", available.join(", "))]
    UnknownScope {
        scope: String,
        available: Vec<String>,
    },

    #[error("App name is required — call .app_name() on the builder")]
    AppNameRequired,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn unknown_key_formats_correctly() {
        let err = ClapfigError::UnknownKey {
            key: "typo_key".into(),
            path: "/home/user/.config/myapp/config.toml".into(),
            line: 42,
        };
        let msg = err.to_string();
        assert!(msg.contains("typo_key"));
        assert!(msg.contains("config.toml"));
        assert!(msg.contains("42"));
    }

    #[test]
    fn key_not_found_formats() {
        let err = ClapfigError::KeyNotFound("database.url".into());
        assert!(err.to_string().contains("database.url"));
    }

    #[test]
    fn app_name_required_formats() {
        let err = ClapfigError::AppNameRequired;
        assert!(err.to_string().contains("app_name"));
    }
}