Skip to main content

lemma/api/
evaluate_data_json.rs

1//! JSON → [`EvaluateDataValue`] tree. The only serde_json conversion into run data.
2
3use crate::evaluation::evaluate_data::{
4    attribute_evaluate_data_error, evaluate_data_from_entries, evaluate_data_map_from_entries,
5    evaluate_data_null_list_item, EvaluateDataValue,
6};
7use crate::Error;
8use indexmap::IndexMap;
9use serde_json::Value;
10
11/// Parse one JSON value into a [`EvaluateDataValue`]. JSON `null` means omit: `Ok(None)`.
12/// A `null` list item is a request error: a list has no unsupplied row.
13///
14/// JSON numbers at any depth are a request error: wire JSON has no exact native type.
15pub fn evaluate_data_value_from_json_value(
16    value: Value,
17) -> Result<Option<EvaluateDataValue>, Error> {
18    let mut frames: Vec<JsonFrame> = Vec::new();
19    let mut step = JsonStep::Descend(value);
20    loop {
21        step = match step {
22            JsonStep::Descend(value) => match value {
23                Value::Null => JsonStep::Deliver(None),
24                Value::String(s) => JsonStep::Deliver(Some(EvaluateDataValue::Literal(s))),
25                Value::Bool(b) => JsonStep::Deliver(Some(EvaluateDataValue::Boolean(b))),
26                Value::Number(n) => {
27                    return Err(Error::request(
28                        format!("pass as string: \"{n}\""),
29                        Some("JSON numbers have no exact native type"),
30                    ))
31                }
32                Value::Object(obj) => {
33                    frames.push(JsonFrame::Object {
34                        entries: Vec::with_capacity(obj.len()),
35                        children: obj.into_iter(),
36                        pending_key: None,
37                    });
38                    JsonStep::NextChild
39                }
40                Value::Array(items) => {
41                    frames.push(JsonFrame::Array {
42                        list: Vec::with_capacity(items.len()),
43                        children: items.into_iter(),
44                    });
45                    JsonStep::NextChild
46                }
47            },
48            JsonStep::NextChild => {
49                let frame = frames
50                    .last_mut()
51                    .expect("BUG: json frame stack empty while reading a child");
52                match frame.next_child() {
53                    Some(child) => JsonStep::Descend(child),
54                    None => {
55                        let frame = frames
56                            .pop()
57                            .expect("BUG: json frame stack empty on completion");
58                        JsonStep::Deliver(Some(frame.finish()?))
59                    }
60                }
61            }
62            JsonStep::Deliver(item) => match frames.last_mut() {
63                None => return Ok(item),
64                Some(JsonFrame::Object {
65                    entries,
66                    pending_key,
67                    ..
68                }) => {
69                    if let Some(parsed) = item {
70                        let key = pending_key
71                            .take()
72                            .expect("BUG: json object child delivered without its key");
73                        entries.push((key, parsed));
74                    }
75                    JsonStep::NextChild
76                }
77                Some(JsonFrame::Array { list, .. }) => {
78                    let parsed =
79                        item.ok_or_else(|| evaluate_data_null_list_item(list.len() + 1))?;
80                    list.push(parsed);
81                    JsonStep::NextChild
82                }
83            },
84        };
85    }
86}
87
88enum JsonStep {
89    Descend(Value),
90    NextChild,
91    Deliver(Option<EvaluateDataValue>),
92}
93
94enum JsonFrame {
95    Object {
96        entries: Vec<(String, EvaluateDataValue)>,
97        children: serde_json::map::IntoIter,
98        pending_key: Option<String>,
99    },
100    Array {
101        list: Vec<EvaluateDataValue>,
102        children: std::vec::IntoIter<Value>,
103    },
104}
105
106impl JsonFrame {
107    fn next_child(&mut self) -> Option<Value> {
108        match self {
109            JsonFrame::Object {
110                children,
111                pending_key,
112                ..
113            } => children.next().map(|(key, child)| {
114                *pending_key = Some(key);
115                child
116            }),
117            JsonFrame::Array { children, .. } => children.next(),
118        }
119    }
120
121    fn finish(self) -> Result<EvaluateDataValue, Error> {
122        match self {
123            JsonFrame::Object { entries, .. } => evaluate_data_map_from_entries(entries),
124            JsonFrame::Array { list, .. } => Ok(EvaluateDataValue::List(list)),
125        }
126    }
127}
128
129/// Optional `data` object from MCP / HTTP JSON → overlay tree for [`crate::Engine::evaluate`].
130/// `null` / omitted → empty map. Non-object → error. JSON `null` values are omitted.
131pub fn parse_evaluate_data_object(
132    data: &Option<Value>,
133) -> Result<IndexMap<String, EvaluateDataValue>, Error> {
134    match data {
135        None | Some(Value::Null) => Ok(IndexMap::new()),
136        Some(Value::Object(map)) => {
137            let mut entries = Vec::with_capacity(map.len());
138            for (key, value) in map {
139                let parsed = evaluate_data_value_from_json_value(value.clone())
140                    .map_err(|e| attribute_evaluate_data_error(e, key))?;
141                if let Some(parsed) = parsed {
142                    entries.push((key.clone(), parsed));
143                }
144            }
145            evaluate_data_from_entries(entries)
146        }
147        Some(_) => Err(Error::request("data must be a JSON object", None::<String>)),
148    }
149}
150
151/// MCP `rules` / HTTP query `?rule=` — one name, a JSON array of names, or omit (all).
152pub fn resolve_evaluate_rules(rules: &Option<Value>) -> Result<Option<Vec<String>>, Error> {
153    match rules {
154        None | Some(Value::Null) => Ok(None),
155        Some(Value::String(s)) => {
156            let name = s.trim();
157            if name.is_empty() {
158                return Err(Error::request("rules must not be empty", None::<String>));
159            }
160            Ok(Some(vec![name.to_string()]))
161        }
162        Some(Value::Array(arr)) => {
163            if arr.is_empty() {
164                return Err(Error::request("rules must not be empty", None::<String>));
165            }
166            let mut names = Vec::with_capacity(arr.len());
167            for (i, v) in arr.iter().enumerate() {
168                match v {
169                    Value::String(s) => {
170                        let name = s.trim();
171                        if name.is_empty() {
172                            return Err(Error::request(
173                                format!("rules[{i}] must not be empty"),
174                                None::<String>,
175                            ));
176                        }
177                        names.push(name.to_string());
178                    }
179                    _ => {
180                        return Err(Error::request(
181                            format!("rules[{i}] must be a string"),
182                            None::<String>,
183                        ));
184                    }
185                }
186            }
187            Ok(Some(names))
188        }
189        Some(_) => Err(Error::request(
190            "rules must be a string or an array of strings",
191            None::<String>,
192        )),
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn json_null_is_omitted() {
202        assert_eq!(
203            evaluate_data_value_from_json_value(Value::Null).expect("null is omit"),
204            None
205        );
206    }
207
208    #[test]
209    fn json_nesting_beyond_serde_limit_fails_to_parse() {
210        let text = format!("{}1{}", "[".repeat(1000), "]".repeat(1000));
211        assert!(serde_json::from_str::<Value>(&text).is_err());
212    }
213
214    #[test]
215    fn json_number_is_request_error() {
216        let err = evaluate_data_value_from_json_value(serde_json::json!(84)).expect_err("number");
217        assert!(err.message().contains("pass as string: \"84\""), "{err}");
218    }
219
220    #[test]
221    fn json_number_error_names_top_level_key() {
222        let err = parse_evaluate_data_object(&Some(serde_json::json!({
223            "price": { "eur": 84 }
224        })))
225        .expect_err("nested number");
226        assert!(err.message().contains("pass as string: \"84\""), "{err}");
227        assert_eq!(err.related_data(), Some("price"));
228    }
229
230    #[test]
231    fn parse_evaluate_data_object_null_field_is_omitted() {
232        let only_null = Some(serde_json::json!({ "x": null }));
233        let empty = parse_evaluate_data_object(&only_null).expect("null field omits");
234        assert!(empty.is_empty(), "got: {empty:?}");
235
236        let mixed = Some(serde_json::json!({ "x": null, "y": "1" }));
237        let map = parse_evaluate_data_object(&mixed).expect("null field omits");
238        assert_eq!(map.len(), 1);
239        assert_eq!(
240            map.get("y"),
241            Some(&EvaluateDataValue::Literal("1".to_string()))
242        );
243        assert!(!map.contains_key("x"));
244    }
245
246    #[test]
247    fn null_list_item_is_request_error_naming_position_and_key() {
248        let err = parse_evaluate_data_object(&Some(serde_json::json!({
249            "fares": ["89 eur", null, "120 eur"]
250        })))
251        .expect_err("null list item");
252        assert_eq!(err.message(), "list item 2 is null");
253        assert_eq!(err.related_data(), Some("fares"));
254    }
255
256    #[test]
257    fn null_nested_list_item_is_request_error() {
258        let err = parse_evaluate_data_object(&Some(serde_json::json!({
259            "components": [{ "own_cost": "5 eur", "components": [null] }]
260        })))
261        .expect_err("nested null list item");
262        assert_eq!(err.message(), "list item 1 is null");
263        assert_eq!(err.related_data(), Some("components"));
264    }
265
266    #[test]
267    fn null_field_inside_list_row_is_omitted() {
268        let parsed = parse_evaluate_data_object(&Some(serde_json::json!({
269            "staff": [{ "name": "Ada", "salary": null }]
270        })))
271        .expect("null row field omits");
272        let Some(EvaluateDataValue::List(rows)) = parsed.get("staff") else {
273            panic!("expected list, got {parsed:?}");
274        };
275        assert_eq!(rows.len(), 1);
276        let EvaluateDataValue::Map(row) = &rows[0] else {
277            panic!("expected row map, got {:?}", rows[0]);
278        };
279        assert_eq!(row.len(), 1);
280        assert_eq!(
281            row.get("name"),
282            Some(&EvaluateDataValue::Literal("Ada".to_string()))
283        );
284    }
285
286    #[test]
287    fn parse_evaluate_data_object_unit_map_and_array() {
288        let parsed = parse_evaluate_data_object(&Some(serde_json::json!({
289            "price": { "eur": "84" },
290            "flags": [true, "x"]
291        })))
292        .expect("object");
293        match parsed.get("price") {
294            Some(EvaluateDataValue::Map(m)) => {
295                assert_eq!(
296                    m.get("eur"),
297                    Some(&EvaluateDataValue::Literal("84".to_string()))
298                );
299            }
300            other => panic!("expected map, got {other:?}"),
301        }
302        match parsed.get("flags") {
303            Some(EvaluateDataValue::List(items)) => {
304                assert_eq!(items[0], EvaluateDataValue::Boolean(true));
305                assert_eq!(items[1], EvaluateDataValue::Literal("x".to_string()));
306            }
307            other => panic!("expected list, got {other:?}"),
308        }
309    }
310
311    #[test]
312    fn duplicate_case_keys_error() {
313        let err = parse_evaluate_data_object(&Some(serde_json::json!({
314            "Qty": "1",
315            "qty": "2"
316        })))
317        .expect_err("dup");
318        assert!(err.message().contains("Duplicate data key"), "{err}");
319    }
320
321    #[test]
322    fn duplicate_case_keys_in_nested_object_error() {
323        let err = parse_evaluate_data_object(&Some(serde_json::json!({
324            "price": { "EUR": "1", "eur": "2" }
325        })))
326        .expect_err("dup");
327        assert!(
328            err.message().contains("Duplicate object key 'eur'"),
329            "{err}"
330        );
331        assert_eq!(err.related_data(), Some("price"));
332    }
333}