Skip to main content

faucet_stream/extract/
jsonpath.rs

1//! JSONPath-based record extraction.
2
3use crate::error::FaucetError;
4use jsonpath_rust::JsonPath;
5use serde_json::Value;
6
7/// Extract records from a JSON response body using a JSONPath expression.
8pub fn extract_records(body: &Value, path: Option<&str>) -> Result<Vec<Value>, FaucetError> {
9    match path {
10        Some(p) => {
11            let results = body
12                .query(p)
13                .map_err(|e| FaucetError::JsonPath(format!("invalid JSONPath '{p}': {e}")))?;
14            Ok(results.into_iter().cloned().collect())
15        }
16        None => match body {
17            Value::Array(arr) => Ok(arr.clone()),
18            other => Ok(vec![other.clone()]),
19        },
20    }
21}
22
23#[cfg(test)]
24mod tests {
25    use super::*;
26    use serde_json::json;
27
28    #[test]
29    fn test_extract_nested_records() {
30        let body = json!({
31            "data": [
32                {"id": 1, "name": "Alice"},
33                {"id": 2, "name": "Bob"},
34            ],
35            "meta": {"total": 2}
36        });
37        let records = extract_records(&body, Some("$.data[*]")).unwrap();
38        assert_eq!(records.len(), 2);
39        assert_eq!(records[0]["name"], "Alice");
40    }
41
42    #[test]
43    fn test_extract_no_path_array() {
44        let body = json!([{"id": 1}, {"id": 2}]);
45        let records = extract_records(&body, None).unwrap();
46        assert_eq!(records.len(), 2);
47    }
48
49    #[test]
50    fn test_extract_no_path_object() {
51        let body = json!({"id": 1, "name": "Alice"});
52        let records = extract_records(&body, None).unwrap();
53        assert_eq!(records.len(), 1);
54        assert_eq!(records[0]["name"], "Alice");
55    }
56
57    #[test]
58    fn test_extract_empty_result() {
59        let body = json!({"data": []});
60        let records = extract_records(&body, Some("$.data[*]")).unwrap();
61        assert!(records.is_empty());
62    }
63}