Skip to main content

camel_language_jsonpath/
lib.rs

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