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