raz_config/
override_config.rs

1use indexmap::IndexMap;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct CommandOverride {
7    pub key: String,
8    pub file_path: Option<PathBuf>,
9    pub function_name: Option<String>,
10    pub line_range: Option<(usize, usize)>,
11    pub env: IndexMap<String, String>,
12    pub cargo_options: Vec<String>,
13    pub rustc_options: Vec<String>,
14    pub args: Vec<String>,
15    pub mode: OverrideMode,
16    pub created_at: chrono::DateTime<chrono::Utc>,
17    pub description: Option<String>,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "lowercase")]
22pub enum OverrideMode {
23    Replace,
24    Append,
25    Prepend,
26    Remove,
27}
28
29impl Default for OverrideMode {
30    fn default() -> Self {
31        Self::Replace
32    }
33}
34
35impl CommandOverride {
36    pub fn new(key: String) -> Self {
37        Self {
38            key,
39            file_path: None,
40            function_name: None,
41            line_range: None,
42            env: IndexMap::new(),
43            cargo_options: Vec::new(),
44            rustc_options: Vec::new(),
45            args: Vec::new(),
46            mode: OverrideMode::default(),
47            created_at: chrono::Utc::now(),
48            description: None,
49        }
50    }
51
52    pub fn with_file(mut self, path: PathBuf) -> Self {
53        self.file_path = Some(path);
54        self
55    }
56
57    pub fn with_function(mut self, name: String) -> Self {
58        self.function_name = Some(name);
59        self
60    }
61
62    pub fn with_line_range(mut self, start: usize, end: usize) -> Self {
63        self.line_range = Some((start, end));
64        self
65    }
66
67    pub fn with_env(mut self, key: String, value: String) -> Self {
68        self.env.insert(key, value);
69        self
70    }
71
72    pub fn with_cargo_option(mut self, option: String) -> Self {
73        self.cargo_options.push(option);
74        self
75    }
76
77    pub fn with_rustc_option(mut self, option: String) -> Self {
78        self.rustc_options.push(option);
79        self
80    }
81
82    pub fn with_arg(mut self, arg: String) -> Self {
83        self.args.push(arg);
84        self
85    }
86
87    pub fn with_mode(mut self, mode: OverrideMode) -> Self {
88        self.mode = mode;
89        self
90    }
91
92    pub fn with_description(mut self, desc: String) -> Self {
93        self.description = Some(desc);
94        self
95    }
96
97    pub fn generate_key(&self) -> String {
98        let mut parts = Vec::new();
99
100        if let Some(path) = &self.file_path {
101            parts.push(path.to_string_lossy().to_string());
102        }
103
104        if let Some(func) = &self.function_name {
105            parts.push(func.clone());
106        }
107
108        if let Some((start, end)) = self.line_range {
109            parts.push(format!("L{start}-{end}"));
110        }
111
112        if parts.is_empty() {
113            self.key.clone()
114        } else {
115            parts.join(":")
116        }
117    }
118
119    pub fn matches_context(
120        &self,
121        file: Option<&PathBuf>,
122        function: Option<&str>,
123        line: Option<usize>,
124    ) -> bool {
125        if let Some(f) = &self.file_path {
126            if let Some(current_file) = file {
127                if f != current_file {
128                    return false;
129                }
130            } else {
131                return false;
132            }
133        }
134
135        if let Some(func) = &self.function_name {
136            if let Some(current_func) = function {
137                if func != current_func {
138                    return false;
139                }
140            } else {
141                return false;
142            }
143        }
144
145        if let Some((start, end)) = self.line_range {
146            if let Some(current_line) = line {
147                if current_line < start || current_line > end {
148                    return false;
149                }
150            } else {
151                return false;
152            }
153        }
154
155        true
156    }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct OverrideCollection {
161    pub overrides: IndexMap<String, CommandOverride>,
162}
163
164impl OverrideCollection {
165    pub fn new() -> Self {
166        Self {
167            overrides: IndexMap::new(),
168        }
169    }
170
171    pub fn add(&mut self, override_config: CommandOverride) {
172        let key = override_config.generate_key();
173        self.overrides.insert(key, override_config);
174    }
175
176    pub fn remove(&mut self, key: &str) -> Option<CommandOverride> {
177        self.overrides.shift_remove(key)
178    }
179
180    pub fn find_best_match(
181        &self,
182        file: Option<&PathBuf>,
183        function: Option<&str>,
184        line: Option<usize>,
185    ) -> Option<&CommandOverride> {
186        self.overrides
187            .values()
188            .filter(|o| o.matches_context(file, function, line))
189            .max_by_key(|o| {
190                let mut score = 0;
191                if o.file_path.is_some() {
192                    score += 100;
193                }
194                if o.function_name.is_some() {
195                    score += 50;
196                }
197                if o.line_range.is_some() {
198                    score += 25;
199                }
200                score
201            })
202    }
203
204    pub fn get_all_for_file(&self, file: &PathBuf) -> Vec<&CommandOverride> {
205        self.overrides
206            .values()
207            .filter(|o| o.file_path.as_ref() == Some(file))
208            .collect()
209    }
210}
211
212impl Default for OverrideCollection {
213    fn default() -> Self {
214        Self::new()
215    }
216}
217
218/// Override system settings
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct OverrideSettings {
221    /// Enable automatic validation before save
222    #[serde(default = "default_validate_before_save")]
223    pub validate_before_save: bool,
224
225    /// Number of backups to keep
226    #[serde(default = "default_max_backups")]
227    pub max_backups: usize,
228
229    /// Prompt for rollback on command failure
230    #[serde(default = "default_prompt_rollback_on_failure")]
231    pub prompt_rollback_on_failure: bool,
232
233    /// Auto-rollback after N consecutive failures
234    #[serde(default = "default_auto_rollback_threshold")]
235    pub auto_rollback_threshold: u32,
236}
237
238impl Default for OverrideSettings {
239    fn default() -> Self {
240        Self {
241            validate_before_save: true,
242            max_backups: 5,
243            prompt_rollback_on_failure: true,
244            auto_rollback_threshold: 3,
245        }
246    }
247}
248
249fn default_validate_before_save() -> bool {
250    true
251}
252
253fn default_max_backups() -> usize {
254    5
255}
256
257fn default_prompt_rollback_on_failure() -> bool {
258    true
259}
260
261fn default_auto_rollback_threshold() -> u32 {
262    3
263}