Skip to main content

cc_audit/config/
loading.rs

1//! Configuration loading functions.
2
3use std::fs;
4use std::path::{Path, PathBuf};
5
6use super::error::ConfigError;
7use super::types::Config;
8
9/// Result of trying to find a configuration file.
10#[derive(Debug)]
11pub struct ConfigLoadResult {
12    /// The loaded configuration.
13    pub config: Config,
14    /// The path to the configuration file, if found.
15    pub path: Option<PathBuf>,
16}
17
18impl Config {
19    /// Load configuration from a file.
20    pub fn from_file(path: &Path) -> Result<Self, ConfigError> {
21        let content = fs::read_to_string(path).map_err(|e| ConfigError::ReadFile {
22            path: path.display().to_string(),
23            source: e,
24        })?;
25
26        let ext = path
27            .extension()
28            .and_then(|e| e.to_str())
29            .unwrap_or("")
30            .to_lowercase();
31
32        match ext.as_str() {
33            "yaml" | "yml" => serde_yaml::from_str(&content).map_err(|e| ConfigError::ParseYaml {
34                path: path.display().to_string(),
35                source: e,
36            }),
37            "json" => serde_json::from_str(&content).map_err(|e| ConfigError::ParseJson {
38                path: path.display().to_string(),
39                source: e,
40            }),
41            "toml" => toml::from_str(&content).map_err(|e| ConfigError::ParseToml {
42                path: path.display().to_string(),
43                source: e,
44            }),
45            _ => Err(ConfigError::UnsupportedFormat(
46                path.display().to_string(),
47                ext,
48            )),
49        }
50    }
51
52    /// Try to find a configuration file in the project directory or parent directories.
53    /// Returns `None` if no configuration file is found.
54    ///
55    /// Search order:
56    /// 1. Walk up from project root to find `.cc-audit.yaml`, `.yml`, `.json`, or `.toml`
57    /// 2. `~/.config/cc-audit/config.yaml`
58    pub fn find_config_file(project_root: Option<&Path>) -> Option<PathBuf> {
59        const CONFIG_FILENAMES: &[&str] = &[
60            ".cc-audit.yaml",
61            ".cc-audit.yml",
62            ".cc-audit.json",
63            ".cc-audit.toml",
64        ];
65
66        // Walk up directory tree to find config file (like git finds .git)
67        if let Some(root) = project_root {
68            let mut current = root;
69            loop {
70                for filename in CONFIG_FILENAMES {
71                    let path = current.join(filename);
72                    if path.exists() {
73                        return Some(path);
74                    }
75                }
76
77                // Move to parent directory
78                match current.parent() {
79                    Some(parent) if !parent.as_os_str().is_empty() => {
80                        current = parent;
81                    }
82                    _ => break,
83                }
84            }
85        }
86
87        // Try global config
88        if let Some(config_dir) = dirs::config_dir() {
89            let global_config = config_dir.join("cc-audit").join("config.yaml");
90            if global_config.exists() {
91                return Some(global_config);
92            }
93        }
94
95        None
96    }
97
98    /// Try to load configuration from the project directory or global config.
99    /// Returns both the configuration and the path where it was found.
100    pub fn try_load(project_root: Option<&Path>) -> ConfigLoadResult {
101        if let Some(path) = Self::find_config_file(project_root)
102            && let Ok(config) = Self::from_file(&path)
103        {
104            return ConfigLoadResult {
105                config,
106                path: Some(path),
107            };
108        }
109
110        ConfigLoadResult {
111            config: Self::default(),
112            path: None,
113        }
114    }
115
116    /// Load configuration from the project directory or global config.
117    /// Returns default configuration if no file is found.
118    ///
119    /// Search order:
120    /// 1. `.cc-audit.yaml` in project root
121    /// 2. `.cc-audit.json` in project root
122    /// 3. `.cc-audit.toml` in project root
123    /// 4. `~/.config/cc-audit/config.yaml`
124    /// 5. Default configuration
125    pub fn load(project_root: Option<&Path>) -> Self {
126        Self::try_load(project_root).config
127    }
128}