Skip to main content

camel_language_jsonpath/
lib.rs

1use camel_language_api::{Body, Exchange, Value};
2use camel_language_api::{Expression, Language, LanguageError, Predicate};
3use jsonpath_rust::JsonPath;
4use serde_json::Value as JsonValue;
5
6pub struct JsonPathLanguage;
7
8struct JsonPathExpression {
9    query: String,
10}
11
12struct JsonPathPredicate {
13    query: String,
14}
15
16fn extract_json(exchange: &Exchange) -> Result<JsonValue, LanguageError> {
17    match &exchange.input.body {
18        Body::Json(v) => Ok(v.clone()),
19        other => other
20            .clone()
21            .try_into_json()
22            .map_err(|e| {
23                LanguageError::EvalError(format!("body is not JSON and cannot be coerced: {e}"))
24            })
25            .and_then(|b| match b {
26                Body::Json(v) => Ok(v),
27                _ => Err(LanguageError::EvalError(
28                    "body coercion did not produce JSON".into(),
29                )),
30            }),
31    }
32}
33
34fn run_query(query: &str, json: &JsonValue) -> Result<JsonValue, LanguageError> {
35    json.query(query)
36        .map_err(|e| LanguageError::EvalError(format!("jsonpath query '{query}' failed: {e}")))
37        .map(|results| match results.len() {
38            0 => JsonValue::Null,
39            1 => results[0].clone(),
40            _ => JsonValue::Array(results.into_iter().cloned().collect()),
41        })
42}
43
44impl Expression for JsonPathExpression {
45    fn evaluate(&self, exchange: &Exchange) -> Result<Value, LanguageError> {
46        let json = extract_json(exchange)?;
47        run_query(&self.query, &json)
48    }
49}
50
51impl Predicate for JsonPathPredicate {
52    fn matches(&self, exchange: &Exchange) -> Result<bool, LanguageError> {
53        let json = extract_json(exchange)?;
54        let result = run_query(&self.query, &json)?;
55        Ok(match &result {
56            JsonValue::Null => false,
57            JsonValue::Bool(b) => *b,
58            JsonValue::Array(arr) => !arr.is_empty(),
59            _ => true,
60        })
61    }
62}
63
64impl Language for JsonPathLanguage {
65    fn name(&self) -> &'static str {
66        "jsonpath"
67    }
68
69    fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError> {
70        let empty = JsonValue::Object(serde_json::Map::new());
71        empty.query(script).map_err(|e| LanguageError::ParseError {
72            expr: script.to_string(),
73            reason: e.to_string(),
74        })?;
75        Ok(Box::new(JsonPathExpression {
76            query: script.to_string(),
77        }))
78    }
79
80    fn create_predicate(&self, script: &str) -> Result<Box<dyn Predicate>, LanguageError> {
81        let empty = JsonValue::Object(serde_json::Map::new());
82        empty.query(script).map_err(|e| LanguageError::ParseError {
83            expr: script.to_string(),
84            reason: e.to_string(),
85        })?;
86        Ok(Box::new(JsonPathPredicate {
87            query: script.to_string(),
88        }))
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use camel_language_api::Message;
96
97    fn exchange_with_json(json: &str) -> Exchange {
98        let value: JsonValue = serde_json::from_str(json).unwrap();
99        Exchange::new(Message::new(Body::Json(value)))
100    }
101
102    fn exchange_with_text_body(text: &str) -> Exchange {
103        Exchange::new(Message::new(Body::Text(text.to_string())))
104    }
105
106    fn empty_exchange() -> Exchange {
107        Exchange::new(Message::default())
108    }
109
110    #[test]
111    fn expression_simple_path() {
112        let lang = JsonPathLanguage;
113        let expr = lang.create_expression("$.store.name").unwrap();
114        let ex = exchange_with_json(r#"{"store":{"name":"books"}}"#);
115        let result = expr.evaluate(&ex).unwrap();
116        assert_eq!(result, JsonValue::String("books".to_string()));
117    }
118
119    #[test]
120    fn expression_nested_path() {
121        let lang = JsonPathLanguage;
122        let expr = lang.create_expression("$.a.b.c").unwrap();
123        let ex = exchange_with_json(r#"{"a":{"b":{"c":42}}}"#);
124        let result = expr.evaluate(&ex).unwrap();
125        assert_eq!(result, JsonValue::Number(42.into()));
126    }
127
128    #[test]
129    fn expression_array_index() {
130        let lang = JsonPathLanguage;
131        let expr = lang.create_expression("$.items[0]").unwrap();
132        let ex = exchange_with_json(r#"{"items":["a","b","c"]}"#);
133        let result = expr.evaluate(&ex).unwrap();
134        assert_eq!(result, JsonValue::String("a".to_string()));
135    }
136
137    #[test]
138    fn expression_wildcard() {
139        let lang = JsonPathLanguage;
140        let expr = lang.create_expression("$.items[*].name").unwrap();
141        let ex = exchange_with_json(r#"{"items":[{"name":"a"},{"name":"b"}]}"#);
142        let result = expr.evaluate(&ex).unwrap();
143        assert_eq!(
144            result,
145            JsonValue::Array(vec![
146                JsonValue::String("a".to_string()),
147                JsonValue::String("b".to_string())
148            ])
149        );
150    }
151
152    #[test]
153    fn expression_root_path() {
154        let lang = JsonPathLanguage;
155        let expr = lang.create_expression("$").unwrap();
156        let ex = exchange_with_json(r#"{"x":1}"#);
157        let result = expr.evaluate(&ex).unwrap();
158        assert_eq!(result["x"], JsonValue::Number(1.into()));
159    }
160
161    #[test]
162    fn expression_text_body_with_valid_json() {
163        let lang = JsonPathLanguage;
164        let expr = lang.create_expression("$.name").unwrap();
165        let ex = exchange_with_text_body(r#"{"name":"test"}"#);
166        let result = expr.evaluate(&ex).unwrap();
167        assert_eq!(result, JsonValue::String("test".to_string()));
168    }
169
170    #[test]
171    fn expression_empty_body_is_error() {
172        let lang = JsonPathLanguage;
173        let expr = lang.create_expression("$.x").unwrap();
174        let ex = empty_exchange();
175        let result = expr.evaluate(&ex);
176        assert!(result.is_err());
177    }
178
179    #[test]
180    fn expression_invalid_jsonpath_syntax() {
181        let lang = JsonPathLanguage;
182        let result = lang.create_expression("$[invalid");
183        let err = match result {
184            Err(e) => e,
185            Ok(_) => panic!("expected ParseError"),
186        };
187        match err {
188            LanguageError::ParseError { expr, reason } => {
189                assert!(!expr.is_empty());
190                assert!(!reason.is_empty());
191            }
192            other => panic!("expected ParseError, got {other:?}"),
193        }
194    }
195
196    #[test]
197    fn predicate_non_empty_array_is_true() {
198        let lang = JsonPathLanguage;
199        let pred = lang.create_predicate("$.items[*]").unwrap();
200        let ex = exchange_with_json(r#"{"items":[1,2,3]}"#);
201        assert!(pred.matches(&ex).unwrap());
202    }
203
204    #[test]
205    fn predicate_empty_result_is_false() {
206        let lang = JsonPathLanguage;
207        let pred = lang.create_predicate("$.missing").unwrap();
208        let ex = exchange_with_json(r#"{"other":1}"#);
209        assert!(!pred.matches(&ex).unwrap());
210    }
211
212    #[test]
213    fn predicate_boolean_true() {
214        let lang = JsonPathLanguage;
215        let pred = lang.create_predicate("$.active").unwrap();
216        let ex = exchange_with_json(r#"{"active":true}"#);
217        assert!(pred.matches(&ex).unwrap());
218    }
219
220    #[test]
221    fn predicate_boolean_false() {
222        let lang = JsonPathLanguage;
223        let pred = lang.create_predicate("$.active").unwrap();
224        let ex = exchange_with_json(r#"{"active":false}"#);
225        assert!(!pred.matches(&ex).unwrap());
226    }
227
228    #[test]
229    fn predicate_found_value_is_true() {
230        let lang = JsonPathLanguage;
231        let pred = lang.create_predicate("$.name").unwrap();
232        let ex = exchange_with_json(r#"{"name":"test"}"#);
233        assert!(pred.matches(&ex).unwrap());
234    }
235}