1use crate::{
2 OverrideEntry,
3 detector::FunctionDetector,
4 error::Result,
5 key::{FunctionContext, OverrideKey},
6 storage::OverrideStorage,
7};
8use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub enum ResolutionStrategy {
13 Exact,
15 Fallback,
17 FuzzyFunction,
19 LineProximity,
21 HashFallback,
23}
24
25pub struct OverrideResolver {
27 storage: OverrideStorage,
28 detector: FunctionDetector,
29 fuzzy_matcher: SkimMatcherV2,
30}
31
32impl OverrideResolver {
33 pub fn new(storage: OverrideStorage) -> Result<Self> {
35 Ok(Self {
36 storage,
37 detector: FunctionDetector::new()?,
38 fuzzy_matcher: SkimMatcherV2::default(),
39 })
40 }
41
42 pub fn resolve(
44 &mut self,
45 context: &FunctionContext,
46 ) -> Result<Option<(OverrideEntry, ResolutionStrategy)>> {
47 let strategies = [
49 ResolutionStrategy::Exact,
50 ResolutionStrategy::Fallback,
51 ResolutionStrategy::FuzzyFunction,
52 ResolutionStrategy::LineProximity,
53 ResolutionStrategy::HashFallback,
54 ];
55
56 for strategy in strategies {
57 if let Some(entry) = self.try_resolve_with_strategy(context, strategy)? {
58 log::debug!("Resolved override using strategy: {strategy:?}");
59 return Ok(Some((entry, strategy)));
60 }
61 }
62
63 Ok(None)
64 }
65
66 fn try_resolve_with_strategy(
68 &mut self,
69 context: &FunctionContext,
70 strategy: ResolutionStrategy,
71 ) -> Result<Option<OverrideEntry>> {
72 match strategy {
73 ResolutionStrategy::Exact => self.resolve_exact(context),
74 ResolutionStrategy::Fallback => self.resolve_fallback(context),
75 ResolutionStrategy::FuzzyFunction => self.resolve_fuzzy_function(context),
76 ResolutionStrategy::LineProximity => self.resolve_line_proximity(context),
77 ResolutionStrategy::HashFallback => self.resolve_hash_fallback(context),
78 }
79 }
80
81 fn resolve_exact(&self, context: &FunctionContext) -> Result<Option<OverrideEntry>> {
83 let key = OverrideKey::new(context)?;
84 self.storage.get_by_primary_key(&key.primary)
85 }
86
87 fn resolve_fallback(&self, context: &FunctionContext) -> Result<Option<OverrideEntry>> {
89 let key = OverrideKey::new(context)?;
90
91 for fallback in &key.fallbacks {
92 if let Some(entry) = self.storage.get_by_key(fallback)? {
93 return Ok(Some(entry));
94 }
95 }
96
97 Ok(None)
98 }
99
100 fn resolve_fuzzy_function(
102 &mut self,
103 context: &FunctionContext,
104 ) -> Result<Option<OverrideEntry>> {
105 let func_name = match &context.function_name {
107 Some(name) => name,
108 None => return Ok(None),
109 };
110
111 let file_overrides = self.storage.get_by_file(&context.file_path)?;
113
114 let mut best_match = None;
116 let mut best_score = 0;
117
118 for entry in file_overrides {
119 if let Some(stored_func) = &entry.metadata.function_name {
120 if let Some(score) = self.fuzzy_matcher.fuzzy_match(stored_func, func_name) {
121 if score > best_score {
122 best_score = score;
123 best_match = Some(entry);
124 }
125 }
126 }
127 }
128
129 if best_score > 50 {
131 Ok(best_match)
132 } else {
133 Ok(None)
134 }
135 }
136
137 fn resolve_line_proximity(
139 &mut self,
140 context: &FunctionContext,
141 ) -> Result<Option<OverrideEntry>> {
142 let source = match std::fs::read_to_string(&context.file_path) {
144 Ok(s) => s,
145 Err(_) => return Ok(None),
146 };
147
148 let current_func = match self
150 .detector
151 .find_function_at_line(&source, context.line_number)?
152 {
153 Some(f) => f,
154 None => return Ok(None),
155 };
156
157 let file_overrides = self.storage.get_by_file(&context.file_path)?;
159
160 for entry in file_overrides {
161 if let Some(stored_func) = &entry.metadata.function_name {
162 if stored_func == ¤t_func.name {
163 return Ok(Some(entry));
164 }
165 }
166 }
167
168 Ok(None)
169 }
170
171 fn resolve_hash_fallback(&self, context: &FunctionContext) -> Result<Option<OverrideEntry>> {
173 let key = OverrideKey::new(context)?;
174
175 if let Some(hash_key) = key.fallbacks.iter().find(|k| k.starts_with("hash:")) {
177 return self.storage.get_by_key(hash_key);
178 }
179
180 Ok(None)
181 }
182
183 pub fn get_resolution_candidates(
185 &mut self,
186 context: &FunctionContext,
187 ) -> Result<Vec<(OverrideEntry, ResolutionStrategy, f32)>> {
188 let mut candidates = Vec::new();
189
190 for strategy in [
192 ResolutionStrategy::Exact,
193 ResolutionStrategy::Fallback,
194 ResolutionStrategy::FuzzyFunction,
195 ResolutionStrategy::LineProximity,
196 ResolutionStrategy::HashFallback,
197 ] {
198 if let Some(entry) = self.try_resolve_with_strategy(context, strategy)? {
199 let confidence = match strategy {
200 ResolutionStrategy::Exact => 1.0,
201 ResolutionStrategy::Fallback => 0.9,
202 ResolutionStrategy::FuzzyFunction => 0.7,
203 ResolutionStrategy::LineProximity => 0.6,
204 ResolutionStrategy::HashFallback => 0.5,
205 };
206
207 candidates.push((entry, strategy, confidence));
208 }
209 }
210
211 Ok(candidates)
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::OverrideMetadata;
219 use raz_config::CommandOverride;
220 use std::path::PathBuf;
221 use tempfile::TempDir;
222
223 #[test]
224 fn test_exact_resolution() {
225 let temp_dir = TempDir::new().unwrap();
226 let storage = OverrideStorage::new(temp_dir.path()).unwrap();
227 let mut resolver = OverrideResolver::new(storage).unwrap();
228
229 let context = FunctionContext {
231 file_path: PathBuf::from("src/main.rs"),
232 function_name: Some("test_func".to_string()),
233 line_number: 10,
234 context: None,
235 };
236
237 let key = OverrideKey::new(&context).unwrap();
238 let override_config = CommandOverride::new("test".to_string());
239 let entry = OverrideEntry {
240 key: key.clone(),
241 override_config,
242 metadata: OverrideMetadata {
243 created_at: chrono::Utc::now(),
244 modified_at: chrono::Utc::now(),
245 file_path: context.file_path.clone(),
246 function_name: context.function_name.clone(),
247 original_line: Some(context.line_number),
248 notes: None,
249 validation_status: crate::ValidationStatus::Pending,
250 failure_count: 0,
251 last_execution_success: None,
252 last_execution_time: None,
253 },
254 };
255
256 resolver.storage.save(&entry).unwrap();
257
258 let result = resolver.resolve(&context).unwrap();
260 assert!(result.is_some());
261
262 let (resolved_entry, strategy) = result.unwrap();
263 assert_eq!(strategy, ResolutionStrategy::Exact);
264 assert_eq!(resolved_entry.key.primary, key.primary);
265 }
266}