Skip to main content

fiddler_script/builtins/
json.rs

1//! JSON processing built-in functions.
2
3use base64::Engine;
4use indexmap::IndexMap;
5
6use crate::error::RuntimeError;
7use crate::Value;
8
9/// Parse JSON from bytes.
10///
11/// # Arguments
12/// - Bytes containing valid JSON
13///
14/// # Returns
15/// - Parsed value (string, integer, boolean, or null)
16pub fn builtin_parse_json(args: Vec<Value>) -> Result<Value, RuntimeError> {
17    if args.len() != 1 {
18        return Err(RuntimeError::wrong_argument_count(1, args.len()));
19    }
20
21    let bytes = match &args[0] {
22        Value::Bytes(b) => b.clone(),
23        Value::String(s) => s.as_bytes().to_vec(),
24        _ => {
25            return Err(RuntimeError::invalid_argument(
26                "parse_json() requires bytes or string argument".to_string(),
27            ))
28        }
29    };
30
31    let json_value: serde_json::Value = serde_json::from_slice(&bytes)
32        .map_err(|e| RuntimeError::invalid_argument(format!("Invalid JSON: {}", e)))?;
33
34    Ok(json_to_value(json_value))
35}
36
37/// Convert a serde_json::Value to a FiddlerScript Value.
38pub fn json_to_value(json: serde_json::Value) -> Value {
39    match json {
40        serde_json::Value::Null => Value::Null,
41        serde_json::Value::Bool(b) => Value::Boolean(b),
42        serde_json::Value::Number(n) => {
43            if let Some(i) = n.as_i64() {
44                Value::Integer(i)
45            } else if let Some(f) = n.as_f64() {
46                // Convert float to integer (truncate)
47                Value::Integer(f as i64)
48            } else {
49                Value::Null
50            }
51        }
52        serde_json::Value::String(s) => Value::String(s),
53        serde_json::Value::Array(arr) => Value::Array(arr.into_iter().map(json_to_value).collect()),
54        serde_json::Value::Object(obj) => {
55            let dict: IndexMap<String, Value> = obj
56                .into_iter()
57                .map(|(k, v)| (k, json_to_value(v)))
58                .collect();
59            Value::Dictionary(dict)
60        }
61    }
62}
63
64/// Convert a FiddlerScript Value to a serde_json::Value.
65pub fn value_to_json(value: &Value) -> serde_json::Value {
66    match value {
67        Value::Null => serde_json::Value::Null,
68        Value::Boolean(b) => serde_json::Value::Bool(*b),
69        Value::Integer(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
70        Value::Float(f) => {
71            if let Some(num) = serde_json::Number::from_f64(*f) {
72                serde_json::Value::Number(num)
73            } else {
74                serde_json::Value::Null
75            }
76        }
77        Value::String(s) => serde_json::Value::String(s.clone()),
78        Value::Bytes(b) => {
79            // Convert bytes to base64 string for JSON representation
80            serde_json::Value::String(base64::engine::general_purpose::STANDARD.encode(b))
81        }
82        Value::Array(arr) => serde_json::Value::Array(arr.iter().map(value_to_json).collect()),
83        Value::Dictionary(dict) => {
84            let obj: serde_json::Map<String, serde_json::Value> = dict
85                .iter()
86                .map(|(k, v)| (k.clone(), value_to_json(v)))
87                .collect();
88            serde_json::Value::Object(obj)
89        }
90    }
91}
92
93/// Query JSON data using JMESPath expressions.
94///
95/// # Arguments
96/// - Value (dictionary or array) - The JSON data to query
97/// - String - The JMESPath expression
98///
99/// # Returns
100/// - Extracted value, or null if not found
101///
102/// # Example
103/// ```ignore
104/// let raw = parse_json(this);
105/// let extracted = jmespath(raw, "embedded.Key");
106/// ```
107pub fn builtin_jmespath(args: Vec<Value>) -> Result<Value, RuntimeError> {
108    if args.len() != 2 {
109        return Err(RuntimeError::wrong_argument_count(2, args.len()));
110    }
111
112    // Get the JMESPath expression string
113    let expression = match &args[1] {
114        Value::String(s) => s.as_str(),
115        _ => {
116            return Err(RuntimeError::invalid_argument(
117                "jmespath() requires a string expression as second argument".to_string(),
118            ))
119        }
120    };
121
122    // Convert FiddlerScript Value to serde_json::Value
123    let json_data = value_to_json(&args[0]);
124
125    // Compile the JMESPath expression
126    let expr = jmespath::compile(expression).map_err(|e| {
127        RuntimeError::invalid_argument(format!("Invalid JMESPath expression: {}", e))
128    })?;
129
130    // Convert serde_json::Value to JMESPath Variable
131    let data = jmespath::Variable::from_serializable(&json_data).map_err(|e| {
132        RuntimeError::invalid_argument(format!(
133            "Failed to convert data to JMESPath variable: {}",
134            e
135        ))
136    })?;
137
138    // Execute the search
139    let result = expr
140        .search(&data)
141        .map_err(|e| RuntimeError::invalid_argument(format!("JMESPath query failed: {}", e)))?;
142
143    // Convert result back to serde_json::Value, then to FiddlerScript Value
144    // The result is an Rc<Variable>, dereference to get the Variable
145    let result_json: serde_json::Value = serde_json::to_value(&*result).map_err(|e| {
146        RuntimeError::invalid_argument(format!("Failed to convert JMESPath result: {}", e))
147    })?;
148
149    Ok(json_to_value(result_json))
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_builtin_parse_json_string() {
158        let json = br#""hello""#.to_vec();
159        let result = builtin_parse_json(vec![Value::Bytes(json)]).unwrap();
160        assert_eq!(result, Value::String("hello".to_string()));
161    }
162
163    #[test]
164    fn test_builtin_parse_json_number() {
165        let json = b"42".to_vec();
166        let result = builtin_parse_json(vec![Value::Bytes(json)]).unwrap();
167        assert_eq!(result, Value::Integer(42));
168    }
169
170    #[test]
171    fn test_builtin_parse_json_boolean() {
172        let json = b"true".to_vec();
173        let result = builtin_parse_json(vec![Value::Bytes(json)]).unwrap();
174        assert_eq!(result, Value::Boolean(true));
175    }
176
177    #[test]
178    fn test_builtin_parse_json_null() {
179        let json = b"null".to_vec();
180        let result = builtin_parse_json(vec![Value::Bytes(json)]).unwrap();
181        assert_eq!(result, Value::Null);
182    }
183
184    #[test]
185    fn test_builtin_parse_json_from_string() {
186        let result =
187            builtin_parse_json(vec![Value::String(r#"{"key": "value"}"#.to_string())]).unwrap();
188        assert!(matches!(result, Value::Dictionary(_)));
189        if let Value::Dictionary(dict) = result {
190            assert_eq!(dict.get("key"), Some(&Value::String("value".to_string())));
191        }
192    }
193
194    #[test]
195    fn test_builtin_parse_json_invalid() {
196        let result = builtin_parse_json(vec![Value::Bytes(b"not valid json".to_vec())]);
197        assert!(matches!(result, Err(RuntimeError::InvalidArgument { .. })));
198    }
199
200    #[test]
201    fn test_value_to_json_null() {
202        let value = Value::Null;
203        let json = value_to_json(&value);
204        assert_eq!(json, serde_json::Value::Null);
205    }
206
207    #[test]
208    fn test_value_to_json_boolean() {
209        let value = Value::Boolean(true);
210        let json = value_to_json(&value);
211        assert_eq!(json, serde_json::Value::Bool(true));
212    }
213
214    #[test]
215    fn test_value_to_json_integer() {
216        let value = Value::Integer(42);
217        let json = value_to_json(&value);
218        assert_eq!(json, serde_json::json!(42));
219    }
220
221    #[test]
222    fn test_value_to_json_float() {
223        let value = Value::Float(3.14);
224        let json = value_to_json(&value);
225        assert_eq!(json, serde_json::json!(3.14));
226    }
227
228    #[test]
229    fn test_value_to_json_string() {
230        let value = Value::String("hello".to_string());
231        let json = value_to_json(&value);
232        assert_eq!(json, serde_json::Value::String("hello".to_string()));
233    }
234
235    #[test]
236    fn test_value_to_json_array() {
237        let value = Value::Array(vec![
238            Value::Integer(1),
239            Value::Integer(2),
240            Value::Integer(3),
241        ]);
242        let json = value_to_json(&value);
243        assert_eq!(json, serde_json::json!([1, 2, 3]));
244    }
245
246    #[test]
247    fn test_value_to_json_dictionary() {
248        let mut dict = IndexMap::new();
249        dict.insert("key".to_string(), Value::String("value".to_string()));
250        let value = Value::Dictionary(dict);
251        let json = value_to_json(&value);
252        assert_eq!(json, serde_json::json!({"key": "value"}));
253    }
254
255    #[test]
256    fn test_builtin_jmespath_simple_key() {
257        let mut dict = IndexMap::new();
258        dict.insert("name".to_string(), Value::String("John".to_string()));
259        dict.insert("age".to_string(), Value::Integer(30));
260        let data = Value::Dictionary(dict);
261
262        let result = builtin_jmespath(vec![data, Value::String("name".to_string())]).unwrap();
263        assert_eq!(result, Value::String("John".to_string()));
264    }
265
266    #[test]
267    fn test_builtin_jmespath_nested_key() {
268        let mut inner_dict = IndexMap::new();
269        inner_dict.insert("bar".to_string(), Value::String("baz".to_string()));
270
271        let mut outer_dict = IndexMap::new();
272        outer_dict.insert("foo".to_string(), Value::Dictionary(inner_dict));
273
274        let data = Value::Dictionary(outer_dict);
275
276        let result = builtin_jmespath(vec![data, Value::String("foo.bar".to_string())]).unwrap();
277        assert_eq!(result, Value::String("baz".to_string()));
278    }
279
280    #[test]
281    fn test_builtin_jmespath_array_index() {
282        let data = Value::Array(vec![
283            Value::Integer(1),
284            Value::Integer(2),
285            Value::Integer(3),
286        ]);
287
288        let result = builtin_jmespath(vec![data, Value::String("[1]".to_string())]).unwrap();
289        assert_eq!(result, Value::Integer(2));
290    }
291
292    #[test]
293    fn test_builtin_jmespath_projection() {
294        let mut dict1 = IndexMap::new();
295        dict1.insert("name".to_string(), Value::String("John".to_string()));
296
297        let mut dict2 = IndexMap::new();
298        dict2.insert("name".to_string(), Value::String("Jane".to_string()));
299
300        let data = Value::Array(vec![Value::Dictionary(dict1), Value::Dictionary(dict2)]);
301
302        let result = builtin_jmespath(vec![data, Value::String("[*].name".to_string())]).unwrap();
303        if let Value::Array(arr) = result {
304            assert_eq!(arr.len(), 2);
305            assert_eq!(arr[0], Value::String("John".to_string()));
306            assert_eq!(arr[1], Value::String("Jane".to_string()));
307        } else {
308            panic!("Expected array result");
309        }
310    }
311
312    #[test]
313    fn test_builtin_jmespath_embedded_key() {
314        let mut inner = IndexMap::new();
315        inner.insert("Key".to_string(), Value::String("value".to_string()));
316
317        let mut outer = IndexMap::new();
318        outer.insert("embedded".to_string(), Value::Dictionary(inner));
319
320        let data = Value::Dictionary(outer);
321
322        let result =
323            builtin_jmespath(vec![data, Value::String("embedded.Key".to_string())]).unwrap();
324        assert_eq!(result, Value::String("value".to_string()));
325    }
326
327    #[test]
328    fn test_builtin_jmespath_nonexistent_key() {
329        let mut dict = IndexMap::new();
330        dict.insert("key".to_string(), Value::String("value".to_string()));
331        let data = Value::Dictionary(dict);
332
333        let result =
334            builtin_jmespath(vec![data, Value::String("nonexistent".to_string())]).unwrap();
335        assert_eq!(result, Value::Null);
336    }
337
338    #[test]
339    fn test_builtin_jmespath_wrong_arg_count() {
340        let result = builtin_jmespath(vec![Value::Null]);
341        assert!(matches!(
342            result,
343            Err(RuntimeError::WrongArgumentCount { .. })
344        ));
345    }
346
347    #[test]
348    fn test_builtin_jmespath_invalid_expression() {
349        let data = Value::Dictionary(IndexMap::new());
350        let result = builtin_jmespath(vec![data, Value::String("[[invalid".to_string())]);
351        assert!(matches!(result, Err(RuntimeError::InvalidArgument { .. })));
352    }
353
354    #[test]
355    fn test_builtin_jmespath_non_string_expression() {
356        let data = Value::Dictionary(IndexMap::new());
357        let result = builtin_jmespath(vec![data, Value::Integer(42)]);
358        assert!(matches!(result, Err(RuntimeError::InvalidArgument { .. })));
359    }
360}