raz_override/
system.rs

1use crate::{
2    CommandOverride, OverrideEntry, OverrideMetadata, ValidationStatus,
3    detector::FunctionDetector,
4    error::{OverrideError, Result},
5    key::{FunctionContext, OverrideKey},
6    preview::OverridePreview,
7    resolver::{OverrideResolver, ResolutionStrategy},
8    storage::OverrideStorage,
9};
10use std::path::{Path, PathBuf};
11
12/// Parameters for creating an override preview
13pub struct PreviewParams<'a> {
14    pub override_config: &'a CommandOverride,
15    pub context: &'a FunctionContext,
16    pub base_command: &'a str,
17    pub base_env: &'a indexmap::IndexMap<String, String>,
18    pub base_cargo_options: &'a [String],
19    pub base_rustc_options: &'a [String],
20    pub base_args: &'a [String],
21}
22
23/// Main override system that coordinates all components
24pub struct OverrideSystem {
25    workspace_path: PathBuf,
26    pub(crate) storage: OverrideStorage,
27    detector: FunctionDetector,
28}
29
30impl OverrideSystem {
31    /// Create a new override system for testing (bypasses hierarchy)
32    #[cfg(test)]
33    pub fn new_for_test(workspace_path: &Path) -> Result<Self> {
34        let storage = OverrideStorage::new_for_test(workspace_path)?;
35        let detector = FunctionDetector::new()?;
36
37        Ok(Self {
38            workspace_path: workspace_path.to_path_buf(),
39            storage,
40            detector,
41        })
42    }
43
44    /// Create a new override system for a workspace
45    pub fn new(workspace_path: &Path) -> Result<Self> {
46        let storage = OverrideStorage::new(workspace_path)?;
47        let detector = FunctionDetector::new()?;
48
49        Ok(Self {
50            workspace_path: workspace_path.to_path_buf(),
51            storage,
52            detector,
53        })
54    }
55
56    /// Generate a stable key from context
57    pub fn generate_key(&mut self, context: &FunctionContext) -> Result<OverrideKey> {
58        // Try to enhance context with detected function name if not provided
59        let enhanced_context = if context.function_name.is_none() {
60            if let Ok(source) = std::fs::read_to_string(&context.file_path) {
61                if let Some(func_info) = self
62                    .detector
63                    .find_function_at_line(&source, context.line_number)?
64                {
65                    FunctionContext {
66                        function_name: Some(func_info.name),
67                        ..context.clone()
68                    }
69                } else {
70                    context.clone()
71                }
72            } else {
73                context.clone()
74            }
75        } else {
76            context.clone()
77        };
78
79        OverrideKey::new(&enhanced_context)
80    }
81
82    /// Save an override with the given key
83    pub fn save_override(
84        &self,
85        key: OverrideKey,
86        override_config: CommandOverride,
87        context: &FunctionContext,
88    ) -> Result<()> {
89        let entry = OverrideEntry {
90            key,
91            override_config,
92            metadata: OverrideMetadata {
93                created_at: chrono::Utc::now(),
94                modified_at: chrono::Utc::now(),
95                file_path: context.file_path.clone(),
96                function_name: context.function_name.clone(),
97                original_line: Some(context.line_number),
98                notes: None,
99                validation_status: crate::ValidationStatus::Pending,
100                last_execution_time: None,
101                last_execution_success: None,
102                failure_count: 0,
103            },
104        };
105
106        self.storage.save(&entry)
107    }
108
109    /// Save an override with validation
110    pub fn save_override_with_validation(
111        &self,
112        key: OverrideKey,
113        override_config: CommandOverride,
114        context: &FunctionContext,
115        command: &str,
116    ) -> Result<()> {
117        // Create entry
118        let entry = OverrideEntry {
119            key,
120            override_config: override_config.clone(),
121            metadata: OverrideMetadata {
122                created_at: chrono::Utc::now(),
123                modified_at: chrono::Utc::now(),
124                file_path: context.file_path.clone(),
125                function_name: context.function_name.clone(),
126                original_line: Some(context.line_number),
127                notes: None,
128                validation_status: crate::ValidationStatus::Pending,
129                last_execution_time: None,
130                last_execution_success: None,
131                failure_count: 0,
132            },
133        };
134
135        // Validate before saving
136        self.storage.save_with_validation(&entry, |entry| {
137            use crate::parser::OverrideParser;
138
139            // Create parser with validation
140            let parser = OverrideParser::new(command);
141
142            // Convert CommandOverride to ParsedOverride for validation
143            let mut parsed = crate::parser::ParsedOverride::new();
144
145            // Convert env vars
146            for (k, v) in &entry.override_config.env {
147                parsed.env.insert(k.clone(), v.clone());
148            }
149
150            // Convert cargo options
151            for opt in &entry.override_config.cargo_options {
152                let parts: Vec<&str> = opt.splitn(2, ' ').collect();
153                if parts.len() == 2 {
154                    parsed.options.insert(
155                        parts[0].to_string(),
156                        crate::parser::OptionValue::Single(parts[1].to_string()),
157                    );
158                } else {
159                    parsed
160                        .options
161                        .insert(opt.clone(), crate::parser::OptionValue::Flag(true));
162                }
163            }
164
165            // Add args
166            parsed.extra_args = entry.override_config.args.clone();
167
168            // Validate
169            parser.validate(&parsed)
170        })
171    }
172
173    /// Resolve an override from context
174    pub fn resolve_override(
175        &mut self,
176        context: &FunctionContext,
177    ) -> Result<Option<CommandOverride>> {
178        let mut resolver = OverrideResolver::new(OverrideStorage::new(&self.workspace_path)?)?;
179        match resolver.resolve(context)? {
180            Some((entry, _strategy)) => Ok(Some(entry.override_config)),
181            None => Ok(None),
182        }
183    }
184
185    /// Resolve an override using the existing storage (for tests)
186    #[cfg(test)]
187    pub fn resolve_override_with_storage(
188        &mut self,
189        context: &FunctionContext,
190    ) -> Result<Option<CommandOverride>> {
191        // For tests, directly check the storage instead of creating a new resolver
192        let entries = self.storage.list_all()?;
193        for entry in entries {
194            if entry.metadata.file_path == context.file_path {
195                if let Some(ref function_name) = context.function_name {
196                    if entry.metadata.function_name.as_ref() == Some(function_name) {
197                        return Ok(Some(entry.override_config));
198                    }
199                } else if entry.metadata.original_line == Some(context.line_number) {
200                    return Ok(Some(entry.override_config));
201                }
202            }
203        }
204        Ok(None)
205    }
206
207    /// Resolve override with strategy information
208    pub fn resolve_with_strategy(
209        &mut self,
210        context: &FunctionContext,
211    ) -> Result<Option<(CommandOverride, ResolutionStrategy)>> {
212        let mut resolver = OverrideResolver::new(OverrideStorage::new(&self.workspace_path)?)?;
213        match resolver.resolve(context)? {
214            Some((entry, strategy)) => Ok(Some((entry.override_config, strategy))),
215            None => Ok(None),
216        }
217    }
218
219    /// Create a preview of changes
220    pub fn preview_override(&self, params: PreviewParams) -> Result<OverridePreview> {
221        let key = OverrideKey::new(params.context)?;
222
223        let entry = OverrideEntry {
224            key,
225            override_config: params.override_config.clone(),
226            metadata: OverrideMetadata {
227                created_at: chrono::Utc::now(),
228                modified_at: chrono::Utc::now(),
229                file_path: params.context.file_path.clone(),
230                function_name: params.context.function_name.clone(),
231                original_line: Some(params.context.line_number),
232                notes: None,
233                validation_status: ValidationStatus::Pending,
234                failure_count: 0,
235                last_execution_success: None,
236                last_execution_time: None,
237            },
238        };
239
240        OverridePreview::new(
241            entry,
242            params.base_command,
243            params.base_env,
244            params.base_cargo_options,
245            params.base_rustc_options,
246            params.base_args,
247        )
248    }
249
250    /// List all overrides
251    pub fn list_all(&self) -> Result<Vec<OverrideEntry>> {
252        self.storage.list_all()
253    }
254
255    /// List overrides for a specific file
256    pub fn list_by_file(&self, file_path: &Path) -> Result<Vec<OverrideEntry>> {
257        self.storage.get_by_file(file_path)
258    }
259
260    /// Delete an override by key
261    pub fn delete_override(&self, key: &str) -> Result<bool> {
262        self.storage.delete(key)
263    }
264
265    /// Clear all overrides
266    pub fn clear_all(&self) -> Result<()> {
267        self.storage.clear()
268    }
269
270    /// Clear overrides for a specific file
271    pub fn clear_by_file(&self, file_path: &Path) -> Result<usize> {
272        self.storage.clear_by_file(file_path)
273    }
274
275    /// Export overrides for backup
276    pub fn export(&self) -> Result<String> {
277        self.storage.export()
278    }
279
280    /// Import overrides from backup
281    pub fn import(&self, data: &str) -> Result<usize> {
282        self.storage.import(data)
283    }
284
285    /// Debug: Get resolution candidates
286    pub fn debug_resolution(
287        &mut self,
288        context: &FunctionContext,
289    ) -> Result<Vec<(OverrideEntry, ResolutionStrategy, f32)>> {
290        let mut resolver = OverrideResolver::new(OverrideStorage::new(&self.workspace_path)?)?;
291        resolver.get_resolution_candidates(context)
292    }
293
294    /// Get function context from file position
295    pub fn get_function_context(
296        &mut self,
297        file_path: &Path,
298        line: usize,
299        column: Option<usize>,
300    ) -> Result<FunctionContext> {
301        let source = std::fs::read_to_string(file_path)?;
302
303        let function_name = self
304            .detector
305            .find_function_at_line(&source, line)?
306            .map(|f| f.name);
307
308        Ok(FunctionContext {
309            file_path: file_path.to_path_buf(),
310            function_name,
311            line_number: line,
312            context: column.map(|c| format!("column:{c}")),
313        })
314    }
315
316    /// Update execution status of an override
317    pub fn update_execution_status(&self, key: &str, success: bool) -> Result<()> {
318        self.storage.update_execution_status(key, success)
319    }
320
321    /// Rollback to last backup
322    pub fn rollback_to_last_backup(&self) -> Result<()> {
323        self.storage.rollback_to_last_backup()
324    }
325
326    /// Get overrides that have failed multiple times
327    pub fn get_problematic_overrides(&self, failure_threshold: u32) -> Result<Vec<OverrideEntry>> {
328        let all_overrides = self.list_all()?;
329        Ok(all_overrides
330            .into_iter()
331            .filter(|entry| entry.metadata.failure_count >= failure_threshold)
332            .collect())
333    }
334
335    /// Check if an override should be auto-rolled back
336    pub fn should_auto_rollback(&self, key: &str, threshold: u32) -> Result<bool> {
337        if let Some(entry) = self.storage.get_by_primary_key(key)? {
338            Ok(entry.metadata.failure_count >= threshold)
339        } else {
340            Ok(false)
341        }
342    }
343}
344
345/// Convenience function to create override system from current directory
346pub fn create_override_system() -> Result<OverrideSystem> {
347    let cwd = std::env::current_dir().map_err(|e| {
348        OverrideError::StorageError(format!("Failed to get current directory: {e}"))
349    })?;
350    OverrideSystem::new(&cwd)
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356    use tempfile::TempDir;
357
358    #[test]
359    fn test_override_system_workflow() {
360        let temp_dir = TempDir::new().unwrap();
361        let mut system = OverrideSystem::new_for_test(temp_dir.path()).unwrap();
362
363        // Create context
364        let context = FunctionContext {
365            file_path: PathBuf::from("src/main.rs"),
366            function_name: Some("handle_request".to_string()),
367            line_number: 42,
368            context: None,
369        };
370
371        // Generate key
372        let key = system.generate_key(&context).unwrap();
373        assert_eq!(key.primary, "src/main.rs:handle_request");
374
375        // Save override
376        let mut override_config = CommandOverride::new("test".to_string());
377        override_config
378            .env
379            .insert("RUST_LOG".to_string(), "debug".to_string());
380
381        system
382            .save_override(key.clone(), override_config.clone(), &context)
383            .unwrap();
384
385        // Resolve override (use test method that works with test storage)
386        let resolved = system.resolve_override_with_storage(&context).unwrap();
387        assert!(resolved.is_some());
388
389        let resolved_config = resolved.unwrap();
390        assert_eq!(resolved_config.key, "test");
391        assert_eq!(
392            resolved_config.env.get("RUST_LOG"),
393            Some(&"debug".to_string())
394        );
395
396        // List overrides
397        let all_overrides = system.list_all().unwrap();
398        assert_eq!(all_overrides.len(), 1);
399
400        // Delete override
401        assert!(system.delete_override(&key.primary).unwrap());
402
403        // Verify deleted
404        let resolved_after = system.resolve_override_with_storage(&context).unwrap();
405        assert!(resolved_after.is_none());
406    }
407
408    #[test]
409    fn test_function_detection_integration() {
410        let temp_dir = TempDir::new().unwrap();
411        let mut system = OverrideSystem::new_for_test(temp_dir.path()).unwrap();
412
413        // Create a test file
414        let test_file = temp_dir.path().join("test.rs");
415        std::fs::write(
416            &test_file,
417            r#"
418fn process_data(input: &str) -> String {
419    input.to_uppercase()
420}
421
422fn main() {
423    let result = process_data("hello");
424    println!("{}", result);
425}
426"#,
427        )
428        .unwrap();
429
430        // Get context for line inside process_data
431        let context = system.get_function_context(&test_file, 2, None).unwrap();
432        assert_eq!(context.function_name.as_deref(), Some("process_data"));
433
434        // Get context for line inside main
435        let context = system.get_function_context(&test_file, 6, None).unwrap();
436        assert_eq!(context.function_name.as_deref(), Some("main"));
437    }
438}