capability_skeleton_string/
string_skeleton_node_parsers.rs

1// ---------------- [ File: capability-skeleton-string/src/string_skeleton_node_parsers.rs ]
2crate::ix!();
3
4fn parse_ordering_if_present(
5    obj: &serde_json::Map<String, serde_json::Value>,
6) -> Result<Option<JustifiedSubBranchOrdering>, FuzzyFromJsonValueError> {
7    if let Some(order_val) = obj.get("ordering") {
8        trace!("(parse_ordering_if_present) Found 'ordering' => unify complex_enum if needed => parse with JustifiedSubBranchOrdering");
9        let mut cloned = order_val.clone();
10        unify_complex_enum_single_variant_inplace(&mut cloned);
11
12        let parsed = JustifiedSubBranchOrdering::fuzzy_from_json_value(&cloned)
13            .map_err(|e| FuzzyFromJsonValueError::Other {
14                target_type: "Dispatch/LeafHolder/Aggregate: ordering",
15                detail: e.to_string(),
16            })?;
17
18        Ok(Some(parsed))
19    } else {
20        debug!("(parse_ordering_if_present) No 'ordering' => returning None");
21        Ok(None)
22    }
23}
24
25/// Detect and rewrite the old "internal‐tag" style into an externally‐tagged enum:
26///
27///   { "variant_name": "Aggregate", "variant_type": "struct_variant", "...": …, "name": …, "children": … }
28///
29/// → turns into
30///
31///   { "Aggregate": { "name": …, "children": …, "...": … } }
32///
33/// Returns the name of the variant if we did the rewrite.
34fn maybe_unify_internal_variant(obj: &mut Map<String, Value>) -> Option<String> {
35    // Look for the +both+ `variant_name` and `variant_type` keys
36    if let (Some(Value::String(var_name)), Some(Value::String(_variant_type))) =
37        (obj.get("variant_name"), obj.get("variant_type"))
38    {
39        // Clone the tag so we own it
40        let tag = var_name.clone();
41
42        // Pull out all of the usual meta fields
43        let meta_confidence   = obj.remove("variant_confidence");
44        let meta_docs         = obj.remove("variant_docs");
45        let meta_justification= obj.remove("variant_justification");
46        let meta_type         = obj.remove("variant_type");
47        // And remove the tag key so it doesn’t end up back in the inner map
48        obj.remove("variant_name");
49
50        // Drain the *rest* of the map into a new inner Map
51        let mut inner = Map::new();
52        for (k,v) in obj.iter() {
53            inner.insert(k.to_string(), v.clone());
54        }
55
56        // Re-insert the meta fields inside the inner Map
57        if let Some(v) = meta_confidence    { inner.insert("variant_confidence".into(),    v); }
58        if let Some(v) = meta_docs          { inner.insert("variant_docs".into(),          v); }
59        if let Some(v) = meta_justification { inner.insert("variant_justification".into(), v); }
60        if let Some(v) = meta_type          { inner.insert("variant_type".into(),          v); }
61
62        // Finally overwrite `obj` with the externally-tagged shape
63        obj.clear();
64        obj.insert(tag.clone(), Value::Object(inner));
65
66        return Some(tag);
67    }
68    None
69}
70
71fn maybe_unify_single_variant_any_keys(obj: &mut Map<String, Value>) {
72    let mut old_obj = mem::take(obj);
73
74    let var_name = match old_obj.remove("variant_name") {
75        Some(Value::String(name)) => name,
76        _ => {
77            *obj = old_obj;
78            return;
79        }
80    };
81
82    let merged_map = old_obj;
83
84    let mut new_top = Map::new();
85    new_top.insert(var_name.clone(), Value::Object(merged_map));
86
87    *obj = new_top;
88
89    trace!(
90        "maybe_unify_single_variant_any_keys => replaced with externally-tagged '{}'",
91        var_name
92    );
93}
94
95/// If `obj["type"] == "complex_enum"` and there's exactly one item in `obj["variants"]`,
96/// unify into externally‐tagged form. e.g.
97///   { "Dispatch": { ... } }
98fn maybe_unify_one_complex_enum(obj: &mut Map<String, Value>) {
99    // Move out the entire map
100    let old_obj = mem::take(obj);
101
102    // Must have type=="complex_enum"
103    let is_complex_enum = old_obj
104        .get("type")
105        .and_then(|v| v.as_str())
106        .map_or(false, |s| s == "complex_enum");
107    if !is_complex_enum {
108        *obj = old_obj;
109        return;
110    }
111
112    let variants_arr = match old_obj.get("variants").and_then(|v| v.as_array()) {
113        Some(arr) => arr,
114        None => {
115            *obj = old_obj;
116            return;
117        }
118    };
119
120    if variants_arr.len() != 1 {
121        *obj = old_obj;
122        return;
123    }
124
125    let single_val = &variants_arr[0];
126    let single_obj = match single_val.as_object() {
127        Some(o) => o,
128        None => {
129            *obj = old_obj;
130            return;
131        }
132    };
133
134    let var_name = single_obj
135        .get("variant_name")
136        .and_then(|v| v.as_str())
137        .unwrap_or("UNKNOWN_VARIANT");
138
139    // We'll build a merged subobject from single_obj. Possibly also copy leftover
140    // meta from old_obj, e.g. "variant_confidence," etc.
141
142    let mut merged_map = Map::new();
143
144    // if there's "fields": { ... }, merge it
145    if let Some(Value::Object(fields_map)) = single_obj.get("fields") {
146        for (k, v) in fields_map {
147            merged_map.insert(k.clone(), v.clone());
148        }
149    }
150
151    // also copy leftover from single_obj except "fields", "variant_name"
152    for (k, v) in single_obj {
153        if k == "fields" || k == "variant_name" {
154            continue;
155        }
156        merged_map.insert(k.clone(), v.clone());
157    }
158
159    // If you want to preserve anything from old_obj (like "enum_docs"), do it here:
160    let mut new_top = Map::new();
161    new_top.insert(var_name.to_string(), Value::Object(merged_map));
162
163    *obj = new_top;
164    trace!(
165        "maybe_unify_one_complex_enum => replaced top-level with externally-tagged '{}'",
166        var_name
167    );
168}
169
170/// Finally, the single public function you call on the entire Value:
171pub fn unify_complex_enum_single_variant_inplace(value: &mut Value) {
172    match value {
173        Value::Object(obj) => {
174            // 1) unify "type":"complex_enum" + single variant
175            maybe_unify_one_complex_enum(obj);
176            // 2) unify if we have "variant_name" + "fields"
177            maybe_unify_single_variant_from_fields(obj);
178            // 3) unify if we have "variant_name" + any other keys
179            maybe_unify_single_variant_any_keys(obj);
180
181            // Recurse deeper
182            if let Value::Object(o2) = value {
183                for child in o2.values_mut() {
184                    unify_complex_enum_single_variant_inplace(child);
185                }
186            }
187        }
188        Value::Array(arr) => {
189            for elem in arr {
190                unify_complex_enum_single_variant_inplace(elem);
191            }
192        }
193        _ => {}
194    }
195}
196
197fn maybe_unify_single_variant_from_fields(obj: &mut Map<String, Value>) {
198    let mut old_obj = mem::take(obj);
199    let original = old_obj.clone();
200
201    let var_name = match old_obj.remove("variant_name") {
202        Some(Value::String(name)) => name,
203        _ => {
204            *obj = original;
205            return;
206        }
207    };
208
209    let fields_map = match old_obj.remove("fields") {
210        Some(Value::Object(m)) => m,
211        _ => {
212            *obj = original;
213            return;
214        }
215    };
216
217    let mut merged_map = Map::new();
218    for (k, v) in fields_map {
219        merged_map.insert(k, v);
220    }
221    for (k, v) in old_obj {
222        merged_map.insert(k, v);
223    }
224
225    let mut new_top = Map::new();
226    new_top.insert(var_name.clone(), Value::Object(merged_map));
227
228    *obj = new_top;
229
230    trace!(
231        "maybe_unify_single_variant_from_fields => replaced with externally-tagged '{}'",
232        var_name
233    );
234}
235
236/// If there's a "nested_template" key in `obj`, we merge all of its
237/// sub-fields into `obj`, then remove "nested_template".
238/// We may also choose to remove or store "generation_instructions".
239fn maybe_unwrap_nested_template(obj: &mut serde_json::Map<String, serde_json::Value>) {
240    // If there's a "nested_template" key, let's merge it into the top-level object
241    if let Some(nested_val) = obj.remove("nested_template") {
242        if let Some(nested_obj) = nested_val.as_object() {
243            for (k, v) in nested_obj {
244                // If there's a conflict on keys, decide whether
245                // nested_obj overrides or top-level overrides. 
246                // Typically we let nested override:
247                obj.insert(k.clone(), v.clone());
248            }
249        } else {
250            // If nested_template is not an object, put it back or ignore it:
251            obj.insert("nested_template".to_owned(), nested_val);
252        }
253    }
254
255    // Now unify "name" if needed
256    maybe_unwrap_name_field(obj);
257
258    // Optionally handle "generation_instructions"
259    if let Some(instr_val) = obj.remove("generation_instructions") {
260        // For example, if you want to store it in variant_justification:
261        if let Some(instr_str) = instr_val.as_str() {
262            let existing_just = obj
263                .get("variant_justification")
264                .and_then(|v| v.as_str())
265                .unwrap_or_default()
266                .to_string();
267
268            let combined_just = if existing_just.is_empty() {
269                instr_str.to_owned()
270            } else {
271                format!("{}; generation_instructions: {}", existing_just, instr_str)
272            };
273
274            obj.insert(
275                "variant_justification".to_owned(),
276                serde_json::Value::String(combined_just),
277            );
278        }
279    }
280}
281
282fn maybe_unwrap_name_field(obj: &mut serde_json::Map<String, serde_json::Value>) {
283    // If there's a "name" key and it's an object with shape
284    //   { "type": "...", "value": "..." }
285    // then unify it to just a string.
286    if let Some(name_val) = obj.get_mut("name") {
287        if let Some(m) = name_val.as_object_mut() {
288            // optional "required": bool => ignoring
289            // check "type":"string"
290            if let Some(Value::String(typ_s)) = m.get("type") {
291                if typ_s == "string" {
292                    if let Some(Value::String(the_value)) = m.get("value") {
293                        // override
294                        let new_str = the_value.clone();
295                        *name_val = Value::String(new_str);
296                    }
297                }
298            }
299        }
300    }
301}
302
303impl FuzzyFromJsonValue for JustifiedStringSkeletonNode {
304    fn fuzzy_from_json_value(value: &serde_json::Value)
305        -> Result<Self, FuzzyFromJsonValueError>
306    {
307        trace!("(JustifiedStringSkeletonNode) Entering fuzzy_from_json_value => {}", value);
308
309        // 1) unwrap nested_template, unify single-variant shapes, etc...
310        let mut cloned = value.clone();
311        if let Some(obj) = cloned.as_object_mut() {
312            maybe_unwrap_nested_template(obj);
313        }
314        unify_complex_enum_single_variant_inplace(&mut cloned);
315
316        // 2) Must be object
317        let obj = match cloned.as_object() {
318            Some(m) => {
319                trace!("(JustifiedStringSkeletonNode) Flattened shape => proceed with variant detection");
320                m
321            }
322            None => {
323                error!("(JustifiedStringSkeletonNode) Not an object => fail");
324                return Err(FuzzyFromJsonValueError::NotAnObject {
325                    target_type: "JustifiedStringSkeletonNode",
326                    actual: cloned,
327                });
328            }
329        };
330
331        let all_keys_vec: Vec<String> = obj.keys().map(|k| k.to_string()).collect();
332        trace!("(JustifiedStringSkeletonNode) top-level keys => {:?}", all_keys_vec);
333
334        // We expect exactly one of "Dispatch","LeafHolder","Aggregate"
335        let mut found_variants = Vec::new();
336        for candidate in ["Dispatch","LeafHolder","Aggregate"] {
337            if obj.contains_key(candidate) {
338                found_variants.push(candidate);
339            }
340        }
341        trace!("(JustifiedStringSkeletonNode) found_variants={:?}", found_variants);
342
343        if found_variants.len() != 1 {
344            error!(
345                "(JustifiedStringSkeletonNode) expected exactly 1 of [Dispatch,LeafHolder,Aggregate], found={:?}",
346                found_variants
347            );
348            return Err(FuzzyFromJsonValueError::Other {
349                target_type: "JustifiedStringSkeletonNode",
350                detail: format!(
351                    "Expected exactly 1 of [Dispatch,LeafHolder,Aggregate], found={:?}, keys={:?}",
352                    found_variants, all_keys_vec
353                ),
354            });
355        }
356
357        let variant_name = found_variants[0];
358        let variant_val = obj.get(variant_name).expect("logic bug in found_variants");
359        let inner_obj = match variant_val.as_object() {
360            Some(m) => m,
361            None => {
362                error!(
363                    "(JustifiedStringSkeletonNode) variant '{}' => must be object => got {:?}",
364                    variant_name, variant_val
365                );
366                return Err(FuzzyFromJsonValueError::Other {
367                    target_type: "JustifiedStringSkeletonNode",
368                    detail: format!("Variant '{}' must map to an object, got {:?}", variant_name, variant_val),
369                });
370            }
371        };
372
373        // parse ordering if present
374        let ordering_parsed = parse_ordering_if_present(inner_obj)?;
375
376        // Dispatch? LeafHolder? Aggregate?
377        match variant_name {
378            "Dispatch" => {
379                trace!("(JustifiedStringSkeletonNode) => Dispatch");
380                let variant_conf = get_f64_field(inner_obj, "variant_confidence", "Dispatch")
381                    .unwrap_or(0.0);
382                let variant_just = get_string_field(inner_obj, "variant_justification", "Dispatch")
383                    .unwrap_or_default();
384
385                let name_val = get_string_field(inner_obj, "name", "Dispatch")?;
386                let name_conf = get_f64_field(inner_obj, "name_confidence", "Dispatch")
387                    .unwrap_or(0.0);
388                let name_just = get_string_field(inner_obj, "name_justification", "Dispatch")
389                    .unwrap_or_default();
390
391                let children_conf = get_f64_field(inner_obj, "children_confidence", "Dispatch")
392                    .unwrap_or(0.0);
393                let children_just = get_string_field(inner_obj, "children_justification", "Dispatch")
394                    .unwrap_or_default();
395
396                // parse children
397                let mut final_children_map = HashMap::new();
398                if let Some(Value::Object(child_map)) = inner_obj.get("children") {
399                    for (child_key, child_value) in child_map {
400                        // Skip known metadata fields:
401                        if child_key == "map_key_template"
402                            || child_key == "map_value_template"
403                            || child_key == "generation_instructions"
404                        {
405                            trace!("(Dispatch) Skipping metadata key='{}'", child_key);
406                            continue;
407                        }
408
409                        trace!("(JustifiedStringSkeletonNode) Dispatch => parse child='{}' => {}", child_key, child_value);
410                        let parsed_child_just = JustifiedDispatchChildSpec::fuzzy_from_json_value(child_value)
411                            .map_err(|e| {
412                                error!("(Dispatch) Could not parse child='{}': {:?}", child_key, e);
413                                FuzzyFromJsonValueError::Other {
414                                    target_type: "Dispatch",
415                                    detail: format!("Error child='{}': {:?}", child_key, e),
416                                }
417                            })?;
418                        final_children_map.insert(child_key.clone(), parsed_child_just.into());
419                    }
420                }
421
422                trace!("(JustifiedStringSkeletonNode) => Dispatch => success => children count={}", final_children_map.len());
423
424                Ok(JustifiedStringSkeletonNode::Dispatch {
425                    variant_confidence: variant_conf,
426                    variant_justification: variant_just,
427                    name: name_val,
428                    name_confidence: name_conf,
429                    name_justification: name_just,
430                    ordering: ordering_parsed,
431                    children: final_children_map,
432                    children_confidence: children_conf,
433                    children_justification: children_just,
434                })
435            }
436
437            "LeafHolder" => {
438                trace!("(JustifiedStringSkeletonNode) => LeafHolder");
439                let variant_conf = get_f64_field(inner_obj, "variant_confidence", "LeafHolder")
440                    .unwrap_or(0.0);
441                let variant_just = get_string_field(inner_obj, "variant_justification", "LeafHolder")
442                    .unwrap_or_default();
443
444                let name_val = get_string_field(inner_obj, "name", "LeafHolder")?;
445                let name_conf = get_f64_field(inner_obj, "name_confidence", "LeafHolder")
446                    .unwrap_or(0.0);
447                let name_just = get_string_field(inner_obj, "name_justification", "LeafHolder")
448                    .unwrap_or_default();
449
450                let n_leaves_val = inner_obj.get("n_leaves").ok_or_else(|| {
451                    error!("(LeafHolder) Missing 'n_leaves' => fail");
452                    FuzzyFromJsonValueError::MissingField {
453                        field_name: "n_leaves",
454                        target_type: "LeafHolder",
455                    }
456                })?;
457                let n_leaves_u8 = fuzzy_u8(n_leaves_val).map_err(|msg| {
458                    error!("(LeafHolder) Could not parse n_leaves => {:?}", msg);
459                    FuzzyFromJsonValueError::Other {
460                        target_type: "LeafHolder",
461                        detail: msg.to_string(),
462                    }
463                })?;
464
465                let capstone_val = inner_obj.get("capstone").ok_or_else(|| {
466                    error!("(LeafHolder) Missing 'capstone' => fail");
467                    FuzzyFromJsonValueError::MissingField {
468                        field_name: "capstone",
469                        target_type: "LeafHolder",
470                    }
471                })?;
472                let capstone_bool = match capstone_val {
473                    Value::Bool(b) => *b,
474                    other => {
475                        error!("(LeafHolder) 'capstone' must be bool => got {:?}", other);
476                        return Err(FuzzyFromJsonValueError::Other {
477                            target_type: "LeafHolder",
478                            detail: format!("Expected 'capstone' to be bool, got {:?}", other),
479                        });
480                    }
481                };
482
483                trace!("(JustifiedStringSkeletonNode) => LeafHolder => success => name='{}', n_leaves={}, capstone={}",
484                    name_val, n_leaves_u8, capstone_bool
485                );
486
487                Ok(JustifiedStringSkeletonNode::LeafHolder {
488                    variant_confidence: variant_conf,
489                    variant_justification: variant_just,
490                    name: name_val,
491                    name_confidence: name_conf,
492                    name_justification: name_just,
493                    ordering: ordering_parsed,
494                    n_leaves: n_leaves_u8,
495                    capstone: capstone_bool,
496                })
497            }
498
499            "Aggregate" => {
500                trace!("(JustifiedStringSkeletonNode) => Aggregate");
501                let variant_conf = get_f64_field(inner_obj, "variant_confidence", "Aggregate")
502                    .unwrap_or(0.0);
503                let variant_just = get_string_field(inner_obj, "variant_justification", "Aggregate")
504                    .unwrap_or_default();
505
506                let name_val = get_string_field(inner_obj, "name", "Aggregate")?;
507                let name_conf = get_f64_field(inner_obj, "name_confidence", "Aggregate")
508                    .unwrap_or(0.0);
509                let name_just = get_string_field(inner_obj, "name_justification", "Aggregate")
510                    .unwrap_or_default();
511
512                let children_conf = get_f64_field(inner_obj, "children_confidence", "Aggregate")
513                    .unwrap_or(0.0);
514                let children_just = get_string_field(inner_obj, "children_justification", "Aggregate")
515                    .unwrap_or_default();
516
517                let mut final_children_map = HashMap::new();
518                if let Some(Value::Object(child_map)) = inner_obj.get("children") {
519                    for (child_key, child_value) in child_map {
520                        // Skip known metadata fields:
521                        if child_key == "map_key_template"
522                            || child_key == "map_value_template"
523                            || child_key == "generation_instructions"
524                        {
525                            trace!("(Aggregate) Skipping metadata key='{}'", child_key);
526                            continue;
527                        }
528
529                        trace!("(Aggregate) parse child='{}' => {}", child_key, child_value);
530                        let parsed_child_just = JustifiedAggregateChildSpec::fuzzy_from_json_value(child_value)
531                            .map_err(|e| {
532                                error!("(Aggregate) Could not parse child='{}': {:?}", child_key, e);
533                                FuzzyFromJsonValueError::Other {
534                                    target_type: "Aggregate",
535                                    detail: format!("Error child='{}': {:?}", child_key, e),
536                                }
537                            })?;
538                        final_children_map.insert(child_key.clone(), parsed_child_just.into());
539                    }
540                }
541
542                trace!("(JustifiedStringSkeletonNode) => Aggregate => success => name='{}', children={}",
543                    name_val, final_children_map.len()
544                );
545
546                Ok(JustifiedStringSkeletonNode::Aggregate {
547                    variant_confidence: variant_conf,
548                    variant_justification: variant_just,
549                    name: name_val,
550                    name_confidence: name_conf,
551                    name_justification: name_just,
552                    ordering: ordering_parsed,
553                    children: final_children_map,
554                    children_confidence: children_conf,
555                    children_justification: children_just,
556                })
557            }
558
559            other => {
560                error!("(JustifiedStringSkeletonNode) Unexpected variant='{}'", other);
561                Err(FuzzyFromJsonValueError::Other {
562                    target_type: "JustifiedStringSkeletonNode",
563                    detail: format!("Unexpected variant key='{}'", other),
564                })
565            }
566        }
567    }
568}