Skip to main content

lemma/serialization/
json.rs

1use crate::planning::semantics::{FactData, FactPath};
2use crate::planning::ExecutionPlan;
3use crate::LemmaError;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_json::Value;
6use std::collections::{HashMap, HashSet};
7
8/// Parse JSON to string values for use with ExecutionPlan::with_values().
9///
10/// - `null` values are skipped
11/// - All other values are converted to their string representation
12pub fn from_json(
13    json: &[u8],
14    _plan: &ExecutionPlan,
15) -> Result<HashMap<String, String>, LemmaError> {
16    let map: HashMap<String, Value> = serde_json::from_slice(json).map_err(|e| {
17        LemmaError::engine(format!("JSON parse error: {}", e), None, None::<String>)
18    })?;
19
20    Ok(map
21        .into_iter()
22        .filter(|(_, v)| !v.is_null())
23        .map(|(k, v)| (k, json_value_to_string(&v)))
24        .collect())
25}
26
27fn json_value_to_string(value: &Value) -> String {
28    match value {
29        Value::String(s) => s.clone(),
30        Value::Number(n) => n.to_string(),
31        Value::Bool(b) => b.to_string(),
32        Value::Array(_) | Value::Object(_) => serde_json::to_string(value).unwrap_or_default(),
33        Value::Null => String::new(),
34    }
35}
36
37/// Serializes HashMap<FactPath, FactData> as array of [FactPath, FactData] tuples.
38pub fn serialize_resolved_fact_value_map<S>(
39    map: &HashMap<FactPath, FactData>,
40    serializer: S,
41) -> Result<S::Ok, S::Error>
42where
43    S: Serializer,
44{
45    let entries: Vec<(&FactPath, &FactData)> = map.iter().collect();
46    entries.serialize(serializer)
47}
48
49/// Deserializes from array of [FactPath, FactData] tuples.
50pub fn deserialize_resolved_fact_value_map<'de, D>(
51    deserializer: D,
52) -> Result<HashMap<FactPath, FactData>, D::Error>
53where
54    D: Deserializer<'de>,
55{
56    let entries: Vec<(FactPath, FactData)> = Vec::deserialize(deserializer)?;
57    Ok(entries.into_iter().collect())
58}
59
60/// Serializes HashSet<FactPath> as array of FactPath structures.
61pub fn serialize_fact_path_set<S>(set: &HashSet<FactPath>, serializer: S) -> Result<S::Ok, S::Error>
62where
63    S: Serializer,
64{
65    let items: Vec<&FactPath> = set.iter().collect();
66    items.serialize(serializer)
67}
68
69/// Deserializes array of FactPath structures to HashSet<FactPath>.
70pub fn deserialize_fact_path_set<'de, D>(deserializer: D) -> Result<HashSet<FactPath>, D::Error>
71where
72    D: Deserializer<'de>,
73{
74    let items: Vec<FactPath> = Vec::deserialize(deserializer)?;
75    Ok(items.into_iter().collect())
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    fn create_test_plan() -> ExecutionPlan {
82        ExecutionPlan {
83            doc_name: "test".to_string(),
84            facts: HashMap::new(),
85            rules: vec![],
86            sources: HashMap::from([("<test>".to_string(), "".to_string())]),
87        }
88    }
89
90    #[test]
91    fn test_json_string_to_string() {
92        let plan = create_test_plan();
93        let json = br#"{"name": "Alice"}"#;
94        let result = from_json(json, &plan).unwrap();
95        assert_eq!(result.get("name"), Some(&"Alice".to_string()));
96    }
97
98    #[test]
99    fn test_json_number_to_string() {
100        let plan = create_test_plan();
101        let json = br#"{"name": 42}"#;
102        let result = from_json(json, &plan).unwrap();
103        assert_eq!(result.get("name"), Some(&"42".to_string()));
104    }
105
106    #[test]
107    fn test_json_boolean_to_string() {
108        let plan = create_test_plan();
109        let json = br#"{"name": true}"#;
110        let result = from_json(json, &plan).unwrap();
111        assert_eq!(result.get("name"), Some(&"true".to_string()));
112    }
113
114    #[test]
115    fn test_json_array_to_string() {
116        let plan = create_test_plan();
117        let json = br#"{"data": [1, 2, 3]}"#;
118        let result = from_json(json, &plan).unwrap();
119        assert_eq!(result.get("data"), Some(&"[1,2,3]".to_string()));
120    }
121
122    #[test]
123    fn test_json_object_to_string() {
124        let plan = create_test_plan();
125        let json = br#"{"config": {"key": "value"}}"#;
126        let result = from_json(json, &plan).unwrap();
127        assert_eq!(
128            result.get("config"),
129            Some(&"{\"key\":\"value\"}".to_string())
130        );
131    }
132
133    #[test]
134    fn test_null_value_skipped() {
135        let plan = create_test_plan();
136        let json = br#"{"name": null, "age": 30}"#;
137        let result = from_json(json, &plan).unwrap();
138        assert_eq!(result.len(), 1);
139        assert!(!result.contains_key("name"));
140        assert_eq!(result.get("age"), Some(&"30".to_string()));
141    }
142
143    #[test]
144    fn test_all_null_values() {
145        let plan = create_test_plan();
146        let json = br#"{"name": null}"#;
147        let result = from_json(json, &plan).unwrap();
148        assert!(result.is_empty());
149    }
150
151    #[test]
152    fn test_mixed_valid_types() {
153        let plan = create_test_plan();
154        let json = br#"{"name": "Test", "count": 5, "active": true, "discount": 21}"#;
155        let result = from_json(json, &plan).unwrap();
156        assert_eq!(result.len(), 4);
157        assert_eq!(result.get("name"), Some(&"Test".to_string()));
158        assert_eq!(result.get("count"), Some(&"5".to_string()));
159        assert_eq!(result.get("active"), Some(&"true".to_string()));
160        assert_eq!(result.get("discount"), Some(&"21".to_string()));
161    }
162
163    #[test]
164    fn test_invalid_json_syntax() {
165        let plan = create_test_plan();
166        let json = br#"{"name": }"#;
167        let result = from_json(json, &plan);
168        assert!(result.is_err());
169        let error_message = result.unwrap_err().to_string();
170        assert!(error_message.contains("JSON parse error"));
171    }
172}