raz_config/
global.rs

1use crate::{
2    CommandConfig, FilterConfig, ProviderConfig, RazConfig, UiConfig,
3    error::{ConfigError, Result},
4    override_config::{OverrideCollection, OverrideSettings},
5    schema::ConfigVersion,
6};
7use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct GlobalConfig {
12    pub raz: RazConfig,
13    pub providers_config: Option<ProviderConfig>,
14    pub filters: Option<FilterConfig>,
15    pub ui: Option<UiConfig>,
16    pub commands: Option<Vec<CommandConfig>>,
17    pub overrides: Option<OverrideSettings>,
18    pub saved_overrides: Option<OverrideCollection>,
19}
20
21impl GlobalConfig {
22    pub fn new() -> Self {
23        Self {
24            raz: RazConfig {
25                version: ConfigVersion::CURRENT,
26                enabled: true,
27                providers: vec!["cargo".to_string(), "rustc".to_string()],
28                cache_dir: None,
29                cache_ttl: Some(3600),
30                parallel_execution: Some(true),
31                max_concurrent_jobs: Some(4),
32            },
33            providers_config: None,
34            filters: None,
35            ui: None,
36            commands: None,
37            overrides: None,
38            saved_overrides: None,
39        }
40    }
41
42    pub fn load() -> Result<Self> {
43        let config_path = Self::config_path()?;
44
45        if !config_path.exists() {
46            return Ok(Self::new());
47        }
48
49        let contents = std::fs::read_to_string(&config_path)?;
50        let config: Self = toml::from_str(&contents)?;
51
52        config.validate()?;
53
54        Ok(config)
55    }
56
57    pub fn save(&self) -> Result<()> {
58        let config_path = Self::config_path()?;
59
60        if let Some(parent) = config_path.parent() {
61            std::fs::create_dir_all(parent)?;
62        }
63
64        let contents = toml::to_string_pretty(self)?;
65        std::fs::write(&config_path, contents)?;
66
67        Ok(())
68    }
69
70    pub fn config_path() -> Result<PathBuf> {
71        let home = dirs::home_dir().ok_or(ConfigError::InvalidHomeDir)?;
72        Ok(home.join(".config").join("raz").join("config.toml"))
73    }
74
75    pub fn validate(&self) -> Result<()> {
76        if self.raz.version.needs_migration(&ConfigVersion::CURRENT) {
77            return Err(ConfigError::VersionMismatch {
78                expected: ConfigVersion::CURRENT.0,
79                found: self.raz.version.0,
80            });
81        }
82
83        if self.raz.providers.is_empty() {
84            return Err(ConfigError::ValidationError(
85                "At least one provider must be enabled".to_string(),
86            ));
87        }
88
89        if let Some(ttl) = self.raz.cache_ttl {
90            if ttl == 0 {
91                return Err(ConfigError::ValidationError(
92                    "Cache TTL must be greater than 0".to_string(),
93                ));
94            }
95        }
96
97        if let Some(jobs) = self.raz.max_concurrent_jobs {
98            if jobs == 0 {
99                return Err(ConfigError::ValidationError(
100                    "Max concurrent jobs must be greater than 0".to_string(),
101                ));
102            }
103        }
104
105        Ok(())
106    }
107
108    pub fn merge_with(&mut self, other: GlobalConfig) {
109        self.raz.enabled = other.raz.enabled;
110
111        if !other.raz.providers.is_empty() {
112            self.raz.providers = other.raz.providers;
113        }
114
115        if other.raz.cache_dir.is_some() {
116            self.raz.cache_dir = other.raz.cache_dir;
117        }
118
119        if other.raz.cache_ttl.is_some() {
120            self.raz.cache_ttl = other.raz.cache_ttl;
121        }
122
123        if other.raz.parallel_execution.is_some() {
124            self.raz.parallel_execution = other.raz.parallel_execution;
125        }
126
127        if other.raz.max_concurrent_jobs.is_some() {
128            self.raz.max_concurrent_jobs = other.raz.max_concurrent_jobs;
129        }
130
131        if other.providers_config.is_some() {
132            self.providers_config = other.providers_config;
133        }
134
135        if other.filters.is_some() {
136            self.filters = other.filters;
137        }
138
139        if other.ui.is_some() {
140            self.ui = other.ui;
141        }
142
143        if other.commands.is_some() {
144            self.commands = other.commands;
145        }
146
147        if other.overrides.is_some() {
148            self.overrides = other.overrides;
149        }
150
151        if other.saved_overrides.is_some() {
152            self.saved_overrides = other.saved_overrides;
153        }
154    }
155}
156
157impl Default for GlobalConfig {
158    fn default() -> Self {
159        Self::new()
160    }
161}