1use crate::planning::semantics::{FactData, FactPath, LiteralValue, ValueKind};
2use crate::Error;
3use indexmap::IndexMap;
4use rust_decimal::Decimal;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_json::Value;
7use std::collections::{HashMap, HashSet};
8
9pub fn from_json(json: &[u8]) -> Result<HashMap<String, String>, Error> {
14 let map: HashMap<String, Value> = serde_json::from_slice(json)
15 .map_err(|e| Error::validation(format!("JSON parse error: {}", e), None, None::<String>))?;
16
17 Ok(map
18 .into_iter()
19 .filter(|(_, v)| !v.is_null())
20 .map(|(k, v)| (k, json_value_to_string(&v)))
21 .collect())
22}
23
24fn json_value_to_string(value: &Value) -> String {
25 match value {
26 Value::String(s) => s.clone(),
27 Value::Number(n) => n.to_string(),
28 Value::Bool(b) => b.to_string(),
29 Value::Array(_) | Value::Object(_) => serde_json::to_string(value)
30 .expect("BUG: serde_json::to_string failed on a serde_json::Value"),
31 Value::Null => String::new(),
32 }
33}
34
35pub fn literal_value_to_json(v: &LiteralValue) -> (Value, Option<String>) {
44 match &v.value {
45 ValueKind::Boolean(b) => (Value::Bool(*b), None),
46 ValueKind::Number(n) => (decimal_to_json(n), None),
47 ValueKind::Scale(n, unit) => (decimal_to_json(n), Some(unit.clone())),
48 ValueKind::Ratio(r, _) => (decimal_to_json(r), None),
49 ValueKind::Duration(n, unit) => (decimal_to_json(n), Some(unit.to_string())),
50 _ => (Value::String(v.display_value()), None),
51 }
52}
53
54fn decimal_to_json(d: &Decimal) -> Value {
58 if d.fract().is_zero() {
59 match i64::try_from(d.trunc()) {
60 Ok(n) => Value::Number(n.into()),
61 Err(_) => Value::String(d.to_string()),
62 }
63 } else {
64 let s = d.to_string();
65 let Ok(f) = s.parse::<f64>() else {
66 return Value::String(s);
67 };
68 match serde_json::Number::from_f64(f) {
69 Some(n) => Value::Number(n),
70 None => Value::String(s),
71 }
72 }
73}
74
75pub fn serialize_resolved_fact_value_map<S>(
81 map: &IndexMap<FactPath, FactData>,
82 serializer: S,
83) -> Result<S::Ok, S::Error>
84where
85 S: Serializer,
86{
87 let entries: Vec<(&FactPath, &FactData)> = map.iter().collect();
88 entries.serialize(serializer)
89}
90
91pub fn deserialize_resolved_fact_value_map<'de, D>(
93 deserializer: D,
94) -> Result<IndexMap<FactPath, FactData>, D::Error>
95where
96 D: Deserializer<'de>,
97{
98 let entries: Vec<(FactPath, FactData)> = Vec::deserialize(deserializer)?;
99 Ok(entries.into_iter().collect())
100}
101
102pub fn serialize_fact_path_set<S>(set: &HashSet<FactPath>, serializer: S) -> Result<S::Ok, S::Error>
104where
105 S: Serializer,
106{
107 let items: Vec<&FactPath> = set.iter().collect();
108 items.serialize(serializer)
109}
110
111pub fn deserialize_fact_path_set<'de, D>(deserializer: D) -> Result<HashSet<FactPath>, D::Error>
113where
114 D: Deserializer<'de>,
115{
116 let items: Vec<FactPath> = Vec::deserialize(deserializer)?;
117 Ok(items.into_iter().collect())
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_json_string_to_string() {
126 let json = br#"{"name": "Alice"}"#;
127 let result = from_json(json).unwrap();
128 assert_eq!(result.get("name"), Some(&"Alice".to_string()));
129 }
130
131 #[test]
132 fn test_json_number_to_string() {
133 let json = br#"{"name": 42}"#;
134 let result = from_json(json).unwrap();
135 assert_eq!(result.get("name"), Some(&"42".to_string()));
136 }
137
138 #[test]
139 fn test_json_boolean_to_string() {
140 let json = br#"{"name": true}"#;
141 let result = from_json(json).unwrap();
142 assert_eq!(result.get("name"), Some(&"true".to_string()));
143 }
144
145 #[test]
146 fn test_json_array_to_string() {
147 let json = br#"{"data": [1, 2, 3]}"#;
148 let result = from_json(json).unwrap();
149 assert_eq!(result.get("data"), Some(&"[1,2,3]".to_string()));
150 }
151
152 #[test]
153 fn test_json_object_to_string() {
154 let json = br#"{"config": {"key": "value"}}"#;
155 let result = from_json(json).unwrap();
156 assert_eq!(
157 result.get("config"),
158 Some(&"{\"key\":\"value\"}".to_string())
159 );
160 }
161
162 #[test]
163 fn test_null_value_skipped() {
164 let json = br#"{"name": null, "age": 30}"#;
165 let result = from_json(json).unwrap();
166 assert_eq!(result.len(), 1);
167 assert!(!result.contains_key("name"));
168 assert_eq!(result.get("age"), Some(&"30".to_string()));
169 }
170
171 #[test]
172 fn test_all_null_values() {
173 let json = br#"{"name": null}"#;
174 let result = from_json(json).unwrap();
175 assert!(result.is_empty());
176 }
177
178 #[test]
179 fn test_mixed_valid_types() {
180 let json = br#"{"name": "Test", "count": 5, "active": true, "discount": 21}"#;
181 let result = from_json(json).unwrap();
182 assert_eq!(result.len(), 4);
183 assert_eq!(result.get("name"), Some(&"Test".to_string()));
184 assert_eq!(result.get("count"), Some(&"5".to_string()));
185 assert_eq!(result.get("active"), Some(&"true".to_string()));
186 assert_eq!(result.get("discount"), Some(&"21".to_string()));
187 }
188
189 #[test]
190 fn test_invalid_json_syntax() {
191 let json = br#"{"name": }"#;
192 let result = from_json(json);
193 assert!(result.is_err());
194 let error_message = result.unwrap_err().to_string();
195 assert!(error_message.contains("JSON parse error"));
196 }
197
198 #[test]
201 fn test_literal_value_to_json_number() {
202 use crate::planning::semantics::LiteralValue;
203 use std::str::FromStr;
204 let v = LiteralValue::number(rust_decimal::Decimal::from_str("42").unwrap());
205 let (val, unit) = literal_value_to_json(&v);
206 assert!(val.is_number());
207 assert_eq!(val.as_i64(), Some(42));
208 assert!(unit.is_none());
209 }
210
211 #[test]
212 fn test_literal_value_to_json_scale() {
213 use crate::planning::semantics::{primitive_scale, LiteralValue};
214 use std::str::FromStr;
215 let v = LiteralValue::scale_with_type(
216 rust_decimal::Decimal::from_str("99.50").unwrap(),
217 "eur".to_string(),
218 primitive_scale().clone(),
219 );
220 let (val, unit) = literal_value_to_json(&v);
221 assert!(val.is_number());
222 assert_eq!(unit.as_deref(), Some("eur"));
223 }
224
225 #[test]
226 fn test_literal_value_to_json_boolean() {
227 use crate::planning::semantics::LiteralValue;
228 let (val, unit) = literal_value_to_json(&LiteralValue::from_bool(true));
229 assert_eq!(val.as_bool(), Some(true));
230 assert!(unit.is_none());
231 }
232
233 #[test]
234 fn test_decimal_to_json_out_of_i64_fallback() {
235 use crate::planning::semantics::LiteralValue;
236 use std::str::FromStr;
237 let huge = rust_decimal::Decimal::from_str("9223372036854775808").unwrap();
239 let v = LiteralValue::number(huge);
240 let (val, _) = literal_value_to_json(&v);
241 assert!(val.is_string());
242 assert_eq!(val.as_str(), Some("9223372036854775808"));
243 }
244}