Skip to main content

fiddler_script/builtins/
json.rs

1//! JSON processing built-in functions.
2
3use indexmap::IndexMap;
4
5use crate::error::RuntimeError;
6use crate::Value;
7
8/// Parse JSON from bytes.
9///
10/// # Arguments
11/// - Bytes containing valid JSON
12///
13/// # Returns
14/// - Parsed value (string, integer, boolean, or null)
15pub fn builtin_parse_json(args: Vec<Value>) -> Result<Value, RuntimeError> {
16    if args.len() != 1 {
17        return Err(RuntimeError::wrong_argument_count(1, args.len()));
18    }
19
20    let bytes = match &args[0] {
21        Value::Bytes(b) => b.clone(),
22        Value::String(s) => s.as_bytes().to_vec(),
23        _ => {
24            return Err(RuntimeError::invalid_argument(
25                "parse_json() requires bytes or string argument".to_string(),
26            ))
27        }
28    };
29
30    let json_value: serde_json::Value = serde_json::from_slice(&bytes)
31        .map_err(|e| RuntimeError::invalid_argument(format!("Invalid JSON: {}", e)))?;
32
33    Ok(json_to_value(json_value))
34}
35
36/// Convert a serde_json::Value to a FiddlerScript Value.
37pub fn json_to_value(json: serde_json::Value) -> Value {
38    match json {
39        serde_json::Value::Null => Value::Null,
40        serde_json::Value::Bool(b) => Value::Boolean(b),
41        serde_json::Value::Number(n) => {
42            if let Some(i) = n.as_i64() {
43                Value::Integer(i)
44            } else if let Some(f) = n.as_f64() {
45                // Convert float to integer (truncate)
46                Value::Integer(f as i64)
47            } else {
48                Value::Null
49            }
50        }
51        serde_json::Value::String(s) => Value::String(s),
52        serde_json::Value::Array(arr) => Value::Array(arr.into_iter().map(json_to_value).collect()),
53        serde_json::Value::Object(obj) => {
54            let dict: IndexMap<String, Value> = obj
55                .into_iter()
56                .map(|(k, v)| (k, json_to_value(v)))
57                .collect();
58            Value::Dictionary(dict)
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_builtin_parse_json_string() {
69        let json = br#""hello""#.to_vec();
70        let result = builtin_parse_json(vec![Value::Bytes(json)]).unwrap();
71        assert_eq!(result, Value::String("hello".to_string()));
72    }
73
74    #[test]
75    fn test_builtin_parse_json_number() {
76        let json = b"42".to_vec();
77        let result = builtin_parse_json(vec![Value::Bytes(json)]).unwrap();
78        assert_eq!(result, Value::Integer(42));
79    }
80
81    #[test]
82    fn test_builtin_parse_json_boolean() {
83        let json = b"true".to_vec();
84        let result = builtin_parse_json(vec![Value::Bytes(json)]).unwrap();
85        assert_eq!(result, Value::Boolean(true));
86    }
87
88    #[test]
89    fn test_builtin_parse_json_null() {
90        let json = b"null".to_vec();
91        let result = builtin_parse_json(vec![Value::Bytes(json)]).unwrap();
92        assert_eq!(result, Value::Null);
93    }
94
95    #[test]
96    fn test_builtin_parse_json_from_string() {
97        let result =
98            builtin_parse_json(vec![Value::String(r#"{"key": "value"}"#.to_string())]).unwrap();
99        assert!(matches!(result, Value::Dictionary(_)));
100        if let Value::Dictionary(dict) = result {
101            assert_eq!(dict.get("key"), Some(&Value::String("value".to_string())));
102        }
103    }
104
105    #[test]
106    fn test_builtin_parse_json_invalid() {
107        let result = builtin_parse_json(vec![Value::Bytes(b"not valid json".to_vec())]);
108        assert!(matches!(result, Err(RuntimeError::InvalidArgument { .. })));
109    }
110}