cc_audit/config/
loading.rs1use std::fs;
4use std::path::{Path, PathBuf};
5
6use super::error::ConfigError;
7use super::types::Config;
8
9#[derive(Debug)]
11pub struct ConfigLoadResult {
12 pub config: Config,
14 pub path: Option<PathBuf>,
16}
17
18impl Config {
19 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 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 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 match current.parent() {
79 Some(parent) if !parent.as_os_str().is_empty() => {
80 current = parent;
81 }
82 _ => break,
83 }
84 }
85 }
86
87 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 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 pub fn load(project_root: Option<&Path>) -> Self {
126 Self::try_load(project_root).config
127 }
128}