ai_json_template/
ai_json_template_for_hashmap.rs

1// ---------------- [ File: ai-json-template/src/ai_json_template_for_hashmap.rs ]
2crate::ix!();
3
4/// Provides a blanket AiJsonTemplate implementation for HashMap<K, V>,
5/// so long as K and V each implement AiJsonTemplate (and K also implements Eq + Hash).
6impl<K, V> AiJsonTemplate for HashMap<K, V>
7where
8    K: Send + Sync + AiJsonTemplate + Eq + Hash + 'static,
9    V: Send + Sync + AiJsonTemplate + 'static,
10{
11    fn to_template() -> JsonValue {
12        // We'll produce a basic "map_of" template with disclaimers
13        trace!("AiJsonTemplate::to_template for HashMap<K, V>");
14
15        let mut obj = serde_json::Map::new();
16        obj.insert("type".to_string(), JsonValue::String("map_of".to_string()));
17
18        // Typically required can be set at the parent struct’s logic, so default to true here:
19        obj.insert("required".to_string(), JsonValue::Bool(true));
20
21        // For the subtemplates: key => K, val => V
22        // But 'K' is also AiJsonTemplate, though in your final schema, you typically just handle "string" keys.
23        // We’ll just store them as "map_key_template" and "map_value_template".
24        let key_schema = K::to_template();
25        let val_schema = V::to_template();
26        obj.insert("map_key_template".to_string(), key_schema);
27        obj.insert("map_value_template".to_string(), val_schema);
28
29        JsonValue::Object(obj)
30    }
31}
32
33/// Similarly, we provide AiJsonTemplateWithJustification for HashMap<K, V>,
34/// requiring that K and V each implement AiJsonTemplateWithJustification.
35impl<K, V> AiJsonTemplateWithJustification for HashMap<K, V>
36where
37    K: Send + Sync + AiJsonTemplate + Eq + Hash + 'static,
38    V: Send + Sync + AiJsonTemplate + 'static,
39{
40    fn to_template_with_justification() -> JsonValue {
41        trace!("AiJsonTemplateWithJustification::to_template_with_justification for HashMap<K, V>");
42
43        // We'll produce "map_of" plus disclaimers for justification
44        let mut obj = serde_json::Map::new();
45        obj.insert("type".to_string(), JsonValue::String("map_of".to_string()));
46        obj.insert("required".to_string(), JsonValue::Bool(true));
47        obj.insert("has_justification".to_string(), JsonValue::Bool(true));
48
49        // subtemplates from K::to_template_with_justification() and V::to_template_with_justification()
50        let nested_key = K::to_template();
51        let nested_val = V::to_template();
52        obj.insert("map_key_template".to_string(), nested_key);
53        obj.insert("map_value_template".to_string(), nested_val);
54
55        JsonValue::Object(obj)
56    }
57}
58
59/// Exhaustive test suite verifying we can handle a HashMap<K, V> for AiJsonTemplate
60/// and AiJsonTemplateWithJustification. We show a K=String, V=Vec<AnnotatedLeaf>-like scenario.
61#[cfg(test)]
62mod test_hashmap_k_v {
63    use super::*;
64
65    /// This struct simulates a nested type, e.g. AnnotatedLeaf. We'll just do a minimal example
66    /// that implements AiJsonTemplate.
67    #[derive(SaveLoad,Serialize,Deserialize,Debug, Clone)]
68    struct FakeLeaf {
69        label: String,
70    }
71
72    impl AiJsonTemplate for FakeLeaf {
73        fn to_template() -> JsonValue {
74            let mut obj = serde_json::Map::new();
75            obj.insert("type".to_string(), JsonValue::String("struct".to_string()));
76            obj.insert("struct_name".to_string(), JsonValue::String("FakeLeaf".into()));
77            let mut fields = serde_json::Map::new();
78            fields.insert("label".to_string(), {
79                let mut label_obj = serde_json::Map::new();
80                label_obj.insert("type".to_string(), JsonValue::String("string".into()));
81                label_obj.insert("required".to_string(), JsonValue::Bool(true));
82                label_obj.insert("generation_instructions".to_string(), JsonValue::String("A label string.".into()));
83                JsonValue::Object(label_obj)
84            });
85            obj.insert("fields".to_string(), JsonValue::Object(fields));
86            JsonValue::Object(obj)
87        }
88    }
89
90    impl AiJsonTemplateWithJustification for FakeLeaf {
91        fn to_template_with_justification() -> JsonValue {
92            let mut root = Self::to_template();
93            if let Some(obj) = root.as_object_mut() {
94                obj.insert("has_justification".to_string(), JsonValue::Bool(true));
95            }
96            root
97        }
98    }
99
100    #[traced_test]
101    fn test_hashmap_string_fakeleaf_schema() {
102        type MyHash = HashMap<String, FakeLeaf>;
103        let schema = <MyHash as AiJsonTemplate>::to_template();
104        assert!(schema.is_object(), "Should produce an object schema");
105
106        let schema_obj = schema.as_object().unwrap();
107        assert_eq!(schema_obj.get("type").unwrap(), "map_of");
108        assert!(schema_obj.contains_key("map_key_template"), "key template expected");
109        assert!(schema_obj.contains_key("map_value_template"), "value template expected");
110    }
111
112    #[traced_test]
113    fn test_hashmap_string_vec_fakeleaf_schema() {
114        // If V=Vec<FakeLeaf>, then we have a map from string => an array of FakeLeaf
115        type MyHash = HashMap<String, Vec<FakeLeaf>>;
116        let schema = <MyHash as AiJsonTemplate>::to_template();
117
118        assert!(schema.is_object(), "Should produce an object schema for map_of");
119        let so = schema.as_object().unwrap();
120        assert_eq!(so.get("type").unwrap(), "map_of");
121        let val_template = so.get("map_value_template").expect("no map_value_template?");
122        assert!(val_template.is_object(), "value template must be an object");
123        let val_obj = val_template.as_object().unwrap();
124        // Should have "type":"array_of", "item_template": <some nested struct stuff>
125        assert!(
126            val_obj.get("type").unwrap().as_str().unwrap().contains("array_of"),
127            "Expected an array_of type"
128        );
129    }
130
131    #[traced_test]
132    fn test_hashmap_justification() {
133        type MyHash = HashMap<String, FakeLeaf>;
134        let jschema = <MyHash as AiJsonTemplateWithJustification>::to_template_with_justification();
135        assert!(jschema.is_object());
136        let m = jschema.as_object().unwrap();
137        assert_eq!(m.get("type").unwrap(), "map_of");
138        assert_eq!(m.get("has_justification").unwrap(), &JsonValue::Bool(true));
139        let kv_template = m.get("map_key_template").unwrap();
140        let vv_template = m.get("map_value_template").unwrap();
141        assert!(kv_template.is_object(), "K justification must be object for the schema");
142        assert!(vv_template.is_object(), "V justification must be object for the schema");
143    }
144}