exif_oxide/processor_registry/
capability.rs1#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum ProcessorCapability {
29 Perfect,
36
37 Good,
44
45 Fallback,
52
53 Incompatible,
58}
59
60impl PartialOrd for ProcessorCapability {
61 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
62 Some(self.cmp(other))
63 }
64}
65
66impl Ord for ProcessorCapability {
67 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
68 self.priority_score().cmp(&other.priority_score())
70 }
71}
72
73impl ProcessorCapability {
74 pub fn is_compatible(&self) -> bool {
76 match self {
77 ProcessorCapability::Perfect => true,
78 ProcessorCapability::Good => true,
79 ProcessorCapability::Fallback => true,
80 ProcessorCapability::Incompatible => false,
81 }
82 }
83
84 pub fn priority_score(&self) -> u8 {
86 match self {
87 ProcessorCapability::Perfect => 100,
88 ProcessorCapability::Good => 75,
89 ProcessorCapability::Fallback => 25,
90 ProcessorCapability::Incompatible => 0,
91 }
92 }
93
94 pub fn description(&self) -> &'static str {
96 match self {
97 ProcessorCapability::Perfect => "Perfect match - optimal processor for this data",
98 ProcessorCapability::Good => "Good match - compatible and recommended",
99 ProcessorCapability::Fallback => "Fallback option - can process but not optimal",
100 ProcessorCapability::Incompatible => "Incompatible - cannot process this data",
101 }
102 }
103
104 pub fn combine(capabilities: &[ProcessorCapability]) -> ProcessorCapability {
109 if capabilities.is_empty() {
110 return ProcessorCapability::Incompatible;
111 }
112
113 if capabilities.contains(&ProcessorCapability::Incompatible) {
115 return ProcessorCapability::Incompatible;
116 }
117
118 capabilities
120 .iter()
121 .min_by_key(|c| c.priority_score())
122 .cloned()
123 .unwrap_or(ProcessorCapability::Incompatible)
124 }
125
126 pub fn from_boolean(is_compatible: bool) -> ProcessorCapability {
130 if is_compatible {
131 ProcessorCapability::Good
132 } else {
133 ProcessorCapability::Incompatible
134 }
135 }
136
137 pub fn from_specificity(
142 manufacturer_matches: bool,
143 model_matches: bool,
144 is_primary_purpose: bool,
145 ) -> ProcessorCapability {
146 if !manufacturer_matches {
147 return ProcessorCapability::Incompatible;
148 }
149
150 match (model_matches, is_primary_purpose) {
151 (true, true) => ProcessorCapability::Perfect,
152 (true, false) => ProcessorCapability::Good,
153 (false, true) => ProcessorCapability::Good,
154 (false, false) => ProcessorCapability::Fallback,
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
165pub struct CapabilityAssessment {
166 pub capability: ProcessorCapability,
168
169 pub reason: String,
171
172 pub factors: Vec<CapabilityFactor>,
174
175 pub missing_requirements: Vec<String>,
177}
178
179impl CapabilityAssessment {
180 pub fn new(capability: ProcessorCapability, reason: String) -> Self {
182 Self {
183 capability,
184 reason,
185 factors: Vec::new(),
186 missing_requirements: Vec::new(),
187 }
188 }
189
190 pub fn add_factor(mut self, factor: CapabilityFactor) -> Self {
192 self.factors.push(factor);
193 self
194 }
195
196 pub fn add_missing_requirement(mut self, requirement: String) -> Self {
198 self.missing_requirements.push(requirement);
199 self
200 }
201
202 pub fn detailed_explanation(&self) -> String {
204 let mut explanation = format!("{}: {}", self.capability.description(), self.reason);
205
206 if !self.factors.is_empty() {
207 explanation.push_str("\nFactors:");
208 for factor in &self.factors {
209 explanation.push_str(&format!("\n - {}", factor.description()));
210 }
211 }
212
213 if !self.missing_requirements.is_empty() {
214 explanation.push_str("\nMissing requirements:");
215 for req in &self.missing_requirements {
216 explanation.push_str(&format!("\n - {req}"));
217 }
218 }
219
220 explanation
221 }
222}
223
224#[derive(Debug, Clone)]
229pub enum CapabilityFactor {
230 ManufacturerMatch(String),
232
233 ModelMatch(String),
235
236 FormatVersionSupported(String),
238
239 RequiredContextAvailable(String),
241
242 TableNameMatch(String),
244
245 DataPatternMatch(String),
247
248 CustomCondition(String, bool),
250}
251
252impl CapabilityFactor {
253 pub fn description(&self) -> String {
255 match self {
256 CapabilityFactor::ManufacturerMatch(manufacturer) => {
257 format!("Manufacturer '{manufacturer}' matches processor target")
258 }
259 CapabilityFactor::ModelMatch(model) => {
260 format!("Model '{model}' matches processor target")
261 }
262 CapabilityFactor::FormatVersionSupported(version) => {
263 format!("Format version '{version}' is supported")
264 }
265 CapabilityFactor::RequiredContextAvailable(field) => {
266 format!("Required context field '{field}' is available")
267 }
268 CapabilityFactor::TableNameMatch(table) => {
269 format!("Table name '{table}' matches processor scope")
270 }
271 CapabilityFactor::DataPatternMatch(pattern) => {
272 format!("Data matches expected pattern '{pattern}'")
273 }
274 CapabilityFactor::CustomCondition(condition, result) => {
275 format!(
276 "Custom condition '{}': {}",
277 condition,
278 if *result { "passed" } else { "failed" }
279 )
280 }
281 }
282 }
283
284 pub fn impact(&self) -> i8 {
286 match self {
287 CapabilityFactor::ManufacturerMatch(_) => 20,
288 CapabilityFactor::ModelMatch(_) => 25,
289 CapabilityFactor::FormatVersionSupported(_) => 15,
290 CapabilityFactor::RequiredContextAvailable(_) => 10,
291 CapabilityFactor::TableNameMatch(_) => 15,
292 CapabilityFactor::DataPatternMatch(_) => 30,
293 CapabilityFactor::CustomCondition(_, true) => 10,
294 CapabilityFactor::CustomCondition(_, false) => -20,
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn test_capability_ordering() {
305 assert!(ProcessorCapability::Perfect > ProcessorCapability::Good);
306 assert!(ProcessorCapability::Good > ProcessorCapability::Fallback);
307 assert!(ProcessorCapability::Fallback > ProcessorCapability::Incompatible);
308 }
309
310 #[test]
311 fn test_capability_priority_scores() {
312 assert_eq!(ProcessorCapability::Perfect.priority_score(), 100);
313 assert_eq!(ProcessorCapability::Good.priority_score(), 75);
314 assert_eq!(ProcessorCapability::Fallback.priority_score(), 25);
315 assert_eq!(ProcessorCapability::Incompatible.priority_score(), 0);
316 }
317
318 #[test]
319 fn test_capability_compatibility() {
320 assert!(ProcessorCapability::Perfect.is_compatible());
321 assert!(ProcessorCapability::Good.is_compatible());
322 assert!(ProcessorCapability::Fallback.is_compatible());
323 assert!(!ProcessorCapability::Incompatible.is_compatible());
324 }
325
326 #[test]
327 fn test_capability_combination() {
328 let capabilities = vec![
329 ProcessorCapability::Perfect,
330 ProcessorCapability::Good,
331 ProcessorCapability::Fallback,
332 ];
333 assert_eq!(
334 ProcessorCapability::combine(&capabilities),
335 ProcessorCapability::Fallback
336 );
337
338 let capabilities = vec![
339 ProcessorCapability::Perfect,
340 ProcessorCapability::Incompatible,
341 ];
342 assert_eq!(
343 ProcessorCapability::combine(&capabilities),
344 ProcessorCapability::Incompatible
345 );
346
347 let capabilities = vec![ProcessorCapability::Perfect, ProcessorCapability::Good];
348 assert_eq!(
349 ProcessorCapability::combine(&capabilities),
350 ProcessorCapability::Good
351 );
352 }
353
354 #[test]
355 fn test_capability_from_specificity() {
356 assert_eq!(
357 ProcessorCapability::from_specificity(false, false, false),
358 ProcessorCapability::Incompatible
359 );
360 assert_eq!(
361 ProcessorCapability::from_specificity(true, true, true),
362 ProcessorCapability::Perfect
363 );
364 assert_eq!(
365 ProcessorCapability::from_specificity(true, false, true),
366 ProcessorCapability::Good
367 );
368 assert_eq!(
369 ProcessorCapability::from_specificity(true, false, false),
370 ProcessorCapability::Fallback
371 );
372 }
373
374 #[test]
375 fn test_capability_assessment() {
376 let assessment = CapabilityAssessment::new(
377 ProcessorCapability::Good,
378 "Manufacturer matches".to_string(),
379 )
380 .add_factor(CapabilityFactor::ManufacturerMatch("Canon".to_string()))
381 .add_missing_requirement("Model information".to_string());
382
383 assert_eq!(assessment.capability, ProcessorCapability::Good);
384 assert_eq!(assessment.factors.len(), 1);
385 assert_eq!(assessment.missing_requirements.len(), 1);
386
387 let explanation = assessment.detailed_explanation();
388 assert!(explanation.contains("Canon"));
389 assert!(explanation.contains("Model information"));
390 }
391
392 #[test]
393 fn test_capability_factor_impact() {
394 let factor = CapabilityFactor::ManufacturerMatch("Canon".to_string());
395 assert_eq!(factor.impact(), 20);
396
397 let factor = CapabilityFactor::CustomCondition("test".to_string(), false);
398 assert_eq!(factor.impact(), -20);
399 }
400}