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