1crate::ix!();
3
4#[derive(
9 Copy,
10 SaveLoad,
11 Debug,
12 Clone,
13 PartialEq,
14 Eq,
15 Serialize,
16 Default,
17 Deserialize,
18 AiJsonTemplate,
19 AiJsonTemplateWithJustification,
20)]
21pub enum CapstoneMode {
22 #[default]
24 Off,
25 Single,
27 Probabilistic,
29}
30
31impl FuzzyFromJsonValue for JustifiedCapstoneMode {
32 fn fuzzy_from_json_value(value: &serde_json::Value) -> Result<Self, FuzzyFromJsonValueError> {
33 trace!("(JustifiedCapstoneMode) Entering fuzzy_from_json_value");
34
35 if value.is_null() {
37 debug!("(JustifiedCapstoneMode) Null => defaulting to Off");
38 return Ok(Self::Off {
39 variant_confidence: 0.0,
40 variant_justification: String::new(),
41 });
42 }
43
44 if let Some(variant_str) = value.as_str() {
46 trace!("(JustifiedCapstoneMode) Found string => '{}'", variant_str);
47 return match variant_str {
48 "Off" => Ok(Self::Off {
49 variant_confidence: 0.0,
50 variant_justification: String::new(),
51 }),
52 "Single" => Ok(Self::Single {
53 variant_confidence: 0.0,
54 variant_justification: String::new(),
55 }),
56 "Probabilistic" => Ok(Self::Probabilistic {
57 variant_confidence: 0.0,
58 variant_justification: String::new(),
59 }),
60 other => {
61 error!("(JustifiedCapstoneMode) Unknown string variant='{}'", other);
62 Err(FuzzyFromJsonValueError::Other {
63 target_type: "JustifiedCapstoneMode",
64 detail: format!(
65 "Unknown string variant='{}'; expected [Off,Single,Probabilistic]",
66 other
67 ),
68 })
69 }
70 };
71 }
72
73 let mut obj = match value.as_object() {
75 Some(m) => {
76 trace!("(JustifiedCapstoneMode) Found object => flattening if any 'fields' sub-objects.");
77 let mut cloned = m.clone();
78 flatten_all_fields(&mut cloned);
79 cloned
80 }
81 None => {
82 error!("(JustifiedCapstoneMode) Not an object => fail!");
83 return Err(FuzzyFromJsonValueError::NotAnObject {
84 target_type: "JustifiedCapstoneMode",
85 actual: value.clone(),
86 });
87 }
88 };
89
90 if obj.len() == 1 && !obj.contains_key("variant_name") {
93 let (maybe_variant, nested) = {
94 let mut iter = obj.into_iter();
95 iter.next().unwrap()
96 };
97 trace!("(JustifiedCapstoneMode) single-key object => key='{}'", maybe_variant);
98
99 let mut sub_obj = match nested.as_object() {
100 Some(sub) => {
101 let mut sclone = sub.clone();
103 flatten_all_fields(&mut sclone);
104 sclone
105 }
106 None => serde_json::Map::new(),
107 };
108
109 let conf = sub_obj
111 .remove("variant_confidence")
112 .and_then(|x| x.as_f64())
113 .unwrap_or(0.0);
114 let just = sub_obj
115 .remove("variant_justification")
116 .and_then(|x| x.as_str().map(|s| s.to_string()))
117 .unwrap_or_default();
118
119 return match maybe_variant.as_str() {
120 "Off" => Ok(Self::Off {
121 variant_confidence: conf,
122 variant_justification: just,
123 }),
124 "Single" => Ok(Self::Single {
125 variant_confidence: conf,
126 variant_justification: just,
127 }),
128 "Probabilistic" => Ok(Self::Probabilistic {
129 variant_confidence: conf,
130 variant_justification: just,
131 }),
132 other => {
133 error!("(JustifiedCapstoneMode) Unknown single-key='{}'", other);
134 Err(FuzzyFromJsonValueError::Other {
135 target_type: "JustifiedCapstoneMode",
136 detail: format!("Unknown single key='{}' for capstone mode", other),
137 })
138 }
139 };
140 }
141
142 let var_name_val = match obj.remove("variant_name") {
144 Some(v) => v,
145 None => {
146 error!("(JustifiedCapstoneMode) Missing 'variant_name' => fail");
147 return Err(FuzzyFromJsonValueError::Other {
148 target_type: "JustifiedCapstoneMode",
149 detail: "No 'variant_name' found => cannot parse Off/Single/Probabilistic".to_string(),
150 });
151 }
152 };
153 let var_name = match var_name_val.as_str() {
154 Some(s) => s,
155 None => {
156 error!("(JustifiedCapstoneMode) 'variant_name' not a string => fail");
157 return Err(FuzzyFromJsonValueError::Other {
158 target_type: "JustifiedCapstoneMode",
159 detail: format!("Expected string for 'variant_name', got {:?}", var_name_val),
160 });
161 }
162 };
163
164 let conf = obj
165 .remove("variant_confidence")
166 .and_then(|v| v.as_f64())
167 .unwrap_or(0.0);
168 let just = obj
169 .remove("variant_justification")
170 .and_then(|v| v.as_str().map(|s| s.to_string()))
171 .unwrap_or_default();
172
173 match var_name {
174 "Off" => Ok(Self::Off {
175 variant_confidence: conf,
176 variant_justification: just,
177 }),
178 "Single" => Ok(Self::Single {
179 variant_confidence: conf,
180 variant_justification: just,
181 }),
182 "Probabilistic" => Ok(Self::Probabilistic {
183 variant_confidence: conf,
184 variant_justification: just,
185 }),
186 other => {
187 error!("(JustifiedCapstoneMode) Unknown variant='{}'", other);
188 Err(FuzzyFromJsonValueError::Other {
189 target_type: "JustifiedCapstoneMode",
190 detail: format!("Unknown variant_name='{}'", other),
191 })
192 }
193 }
194 }
195}
196
197#[cfg(test)]
198mod test_fuzzy_from_json_value_for_justified_capstone_mode {
199 use super::*;
200 use serde_json::json;
201 use tracing::{debug, error, info, trace};
202 use traced_test::traced_test;
203
204 #[traced_test]
205 fn test_null_input() {
206 trace!("test_null_input: starting");
207 let input = json!(null);
208 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
209 assert!(
210 result.is_ok(),
211 "Expected Ok for null input, got error: {:?}",
212 result.err()
213 );
214 let parsed = result.unwrap();
215 assert!(
216 matches!(parsed, JustifiedCapstoneMode::Off {..}),
217 "Expected Off variant for null input, got: {:?}",
218 parsed
219 );
220 if let JustifiedCapstoneMode::Off {
221 variant_confidence,
222 variant_justification,
223 } = parsed
224 {
225 assert_eq!(variant_confidence, 0.0, "Expected 0.0 confidence for null input");
226 assert_eq!(
227 variant_justification, "",
228 "Expected empty justification for null input"
229 );
230 }
231 }
232
233 #[traced_test]
234 fn test_string_off() {
235 trace!("test_string_off: starting");
236 let input = json!("Off");
237 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
238 assert!(
239 result.is_ok(),
240 "Expected Ok for string 'Off', got error: {:?}",
241 result.err()
242 );
243 let parsed = result.unwrap();
244 assert!(
245 matches!(parsed, JustifiedCapstoneMode::Off {..}),
246 "Expected Off variant, got: {:?}",
247 parsed
248 );
249 }
250
251 #[traced_test]
252 fn test_string_single() {
253 trace!("test_string_single: starting");
254 let input = json!("Single");
255 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
256 assert!(
257 result.is_ok(),
258 "Expected Ok for string 'Single', got error: {:?}",
259 result.err()
260 );
261 let parsed = result.unwrap();
262 assert!(
263 matches!(parsed, JustifiedCapstoneMode::Single {..}),
264 "Expected Single variant, got: {:?}",
265 parsed
266 );
267 }
268
269 #[traced_test]
270 fn test_string_probabilistic() {
271 trace!("test_string_probabilistic: starting");
272 let input = json!("Probabilistic");
273 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
274 assert!(
275 result.is_ok(),
276 "Expected Ok for string 'Probabilistic', got error: {:?}",
277 result.err()
278 );
279 let parsed = result.unwrap();
280 assert!(
281 matches!(parsed, JustifiedCapstoneMode::Probabilistic {..}),
282 "Expected Probabilistic variant, got: {:?}",
283 parsed
284 );
285 }
286
287 #[traced_test]
288 fn test_single_key_object_off() {
289 trace!("test_single_key_object_off: starting");
290 let input = json!({
291 "Off": {
292 "variant_confidence": 0.9,
293 "variant_justification": "Felt best to turn capstones off"
294 }
295 });
296 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
297 assert!(
298 result.is_ok(),
299 "Expected Ok for single-key object 'Off', got error: {:?}",
300 result.err()
301 );
302 let parsed = result.unwrap();
303 match parsed {
304 JustifiedCapstoneMode::Off {
305 variant_confidence,
306 variant_justification,
307 } => {
308 assert_eq!(
309 variant_confidence, 0.9,
310 "Expected 0.9 confidence from single-key object"
311 );
312 assert_eq!(
313 variant_justification,
314 "Felt best to turn capstones off",
315 "Justification mismatch"
316 );
317 }
318 _ => panic!("Expected Off variant, got: {:?}", parsed),
319 }
320 }
321
322 #[traced_test]
323 fn test_single_key_object_single() {
324 trace!("test_single_key_object_single: starting");
325 let input = json!({
326 "Single": {
327 "variant_confidence": 0.8,
328 "variant_justification": "A single apex skill is perfect"
329 }
330 });
331 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
332 assert!(
333 result.is_ok(),
334 "Expected Ok for single-key object 'Single', got error: {:?}",
335 result.err()
336 );
337 let parsed = result.unwrap();
338 match parsed {
339 JustifiedCapstoneMode::Single {
340 variant_confidence,
341 variant_justification,
342 } => {
343 assert_eq!(
344 variant_confidence, 0.8,
345 "Expected 0.8 confidence from single-key object"
346 );
347 assert_eq!(
348 variant_justification,
349 "A single apex skill is perfect",
350 "Justification mismatch"
351 );
352 }
353 _ => panic!("Expected Single variant, got: {:?}", parsed),
354 }
355 }
356
357 #[traced_test]
358 fn test_single_key_object_probabilistic() {
359 trace!("test_single_key_object_probabilistic: starting");
360 let input = json!({
361 "Probabilistic": {
362 "variant_confidence": 0.7,
363 "variant_justification": "Some portion of leaves should be apex skills"
364 }
365 });
366 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
367 assert!(
368 result.is_ok(),
369 "Expected Ok for single-key object 'Probabilistic', got error: {:?}",
370 result.err()
371 );
372 let parsed = result.unwrap();
373 match parsed {
374 JustifiedCapstoneMode::Probabilistic {
375 variant_confidence,
376 variant_justification,
377 } => {
378 assert_eq!(
379 variant_confidence, 0.7,
380 "Expected 0.7 confidence from single-key object"
381 );
382 assert_eq!(
383 variant_justification,
384 "Some portion of leaves should be apex skills",
385 "Justification mismatch"
386 );
387 }
388 _ => panic!("Expected Probabilistic variant, got: {:?}", parsed),
389 }
390 }
391
392 #[traced_test]
393 fn test_standard_object_probabilistic() {
394 trace!("test_standard_object_probabilistic: starting");
395 let input = json!({
396 "variant_name": "Probabilistic",
397 "variant_confidence": 0.42,
398 "variant_justification": "Some fraction of leaves qualify"
399 });
400 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
401 assert!(
402 result.is_ok(),
403 "Expected Ok for standard object 'Probabilistic', got error: {:?}",
404 result.err()
405 );
406 let parsed = result.unwrap();
407 match parsed {
408 JustifiedCapstoneMode::Probabilistic {
409 variant_confidence,
410 variant_justification,
411 } => {
412 assert_eq!(variant_confidence, 0.42, "Confidence mismatch");
413 assert_eq!(
414 variant_justification, "Some fraction of leaves qualify",
415 "Justification mismatch"
416 );
417 }
418 _ => panic!("Expected Probabilistic variant, got: {:?}", parsed),
419 }
420 }
421
422 #[traced_test]
423 fn test_standard_object_off() {
424 trace!("test_standard_object_off: starting");
425 let input = json!({
426 "variant_name": "Off",
427 "variant_confidence": 1.0,
428 "variant_justification": "Force capstones off entirely"
429 });
430 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
431 assert!(
432 result.is_ok(),
433 "Expected Ok for standard object 'Off', got error: {:?}",
434 result.err()
435 );
436 let parsed = result.unwrap();
437 match parsed {
438 JustifiedCapstoneMode::Off {
439 variant_confidence,
440 variant_justification,
441 } => {
442 assert_eq!(variant_confidence, 1.0, "Confidence mismatch");
443 assert_eq!(
444 variant_justification,
445 "Force capstones off entirely",
446 "Justification mismatch"
447 );
448 }
449 _ => panic!("Expected Off variant, got: {:?}", parsed),
450 }
451 }
452
453 #[traced_test]
454 fn test_unknown_variant_string() {
455 trace!("test_unknown_variant_string: starting");
456 let input = json!("RandomStuff");
457 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
458 assert!(
459 result.is_err(),
460 "Expected an error for unknown string variant, got Ok: {:?}",
461 result.ok()
462 );
463 }
464
465 #[traced_test]
466 fn test_unknown_variant_key() {
467 trace!("test_unknown_variant_key: starting");
468 let input = json!({
469 "RandomStuff": {
470 "variant_confidence": 0.5,
471 "variant_justification": "No idea what this is"
472 }
473 });
474 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
475 assert!(
476 result.is_err(),
477 "Expected an error for unknown single-key variant, got Ok: {:?}",
478 result.ok()
479 );
480 }
481
482 #[traced_test]
483 fn test_missing_variant_name_in_object() {
484 trace!("test_missing_variant_name_in_object: starting");
485 let input = json!({
486 "variant_confidence": 0.5,
487 "variant_justification": "Missing the variant_name field"
488 });
489 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
490 assert!(
491 result.is_err(),
492 "Expected an error for missing 'variant_name', got Ok: {:?}",
493 result.ok()
494 );
495 }
496
497 #[traced_test]
498 fn test_non_object_non_string() {
499 trace!("test_non_object_non_string: starting");
500 let input = json!(123);
501 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
502 assert!(
503 result.is_err(),
504 "Expected an error for numeric input, got Ok: {:?}",
505 result.ok()
506 );
507 }
508
509 #[traced_test]
510 fn test_malformed_variant_name_type() {
511 trace!("test_malformed_variant_name_type: starting");
512 let input = json!({
513 "variant_name": 123
514 });
515 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
516 assert!(
517 result.is_err(),
518 "Expected an error for integer 'variant_name', got Ok: {:?}",
519 result.ok()
520 );
521 }
522
523 #[traced_test]
524 fn test_non_number_variant_confidence() {
525 trace!("test_non_number_variant_confidence: starting");
526 let input = json!({
527 "variant_name": "Single",
528 "variant_confidence": "not a number",
529 "variant_justification": "But let's see if it defaults"
530 });
531 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
532 assert!(
533 result.is_ok(),
534 "Expected Ok even if variant_confidence is not numeric, got error: {:?}",
535 result.err()
536 );
537 let parsed = result.unwrap();
538 match parsed {
539 JustifiedCapstoneMode::Single {
540 variant_confidence,
541 variant_justification,
542 } => {
543 assert_eq!(
544 variant_confidence, 0.0,
545 "Expected confidence to default to 0.0 if not numeric"
546 );
547 assert_eq!(
548 variant_justification, "But let's see if it defaults",
549 "Justification mismatch"
550 );
551 }
552 _ => panic!("Expected Single variant, got: {:?}", parsed),
553 }
554 }
555
556 #[traced_test]
557 fn test_non_string_variant_justification() {
558 trace!("test_non_string_variant_justification: starting");
559 let input = json!({
560 "variant_name": "Probabilistic",
561 "variant_confidence": 0.9,
562 "variant_justification": 999
563 });
564 let result = JustifiedCapstoneMode::fuzzy_from_json_value(&input);
565 assert!(
566 result.is_ok(),
567 "Expected Ok even if variant_justification is not a string, got error: {:?}",
568 result.err()
569 );
570 let parsed = result.unwrap();
571 match parsed {
572 JustifiedCapstoneMode::Probabilistic {
573 variant_confidence,
574 variant_justification,
575 } => {
576 assert_eq!(variant_confidence, 0.9, "Confidence mismatch");
577 assert_eq!(
578 variant_justification, "",
579 "Expected empty justification if not a valid string"
580 );
581 }
582 _ => panic!("Expected Probabilistic variant, got: {:?}", parsed),
583 }
584 }
585}