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}