camel_language_jsonpath/
lib.rs1use 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}