1use crate::JpxEngine;
7use crate::error::{EngineError, Result};
8use crate::types::{BatchEvaluateResult, BatchExpressionResult, ValidationResult};
9use serde_json::Value;
10
11impl JpxEngine {
12 pub fn evaluate(&self, expression: &str, input: &Value) -> Result<Value> {
47 let expr = self
48 .runtime
49 .compile(expression)
50 .map_err(|e| EngineError::InvalidExpression(e.to_string()))?;
51
52 if self.strict && crate::explain::has_let_nodes(expr.as_ast()) {
53 return Err(EngineError::InvalidExpression(
54 "Let expressions are not available in strict mode (standard JMESPath only). \
55 Remove --strict to use let expressions."
56 .to_string(),
57 ));
58 }
59
60 let result = expr
61 .search(input)
62 .map_err(|e| EngineError::evaluation_failed(e.to_string()))?;
63
64 Ok(result)
65 }
66
67 pub fn evaluate_str(&self, expression: &str, input: &str) -> Result<Value> {
87 let json: Value =
88 serde_json::from_str(input).map_err(|e| EngineError::InvalidJson(e.to_string()))?;
89 self.evaluate(expression, &json)
90 }
91
92 pub fn batch_evaluate(&self, expressions: &[String], input: &Value) -> BatchEvaluateResult {
118 let results = expressions
119 .iter()
120 .map(|expr| match self.evaluate(expr, input) {
121 Ok(result) => BatchExpressionResult {
122 expression: expr.clone(),
123 result: Some(result),
124 error: None,
125 },
126 Err(e) => BatchExpressionResult {
127 expression: expr.clone(),
128 result: None,
129 error: Some(e.to_string()),
130 },
131 })
132 .collect();
133
134 BatchEvaluateResult { results }
135 }
136
137 pub fn validate(&self, expression: &str) -> ValidationResult {
160 match jpx_core::compile(expression) {
161 Ok(expr) => {
162 if self.strict {
163 if crate::explain::has_let_nodes(expr.as_ast()) {
165 return ValidationResult {
166 valid: false,
167 error: Some(
168 "Let expressions are not available in strict mode \
169 (standard JMESPath only)."
170 .to_string(),
171 ),
172 };
173 }
174
175 let func_names = crate::explain::collect_function_names(expr.as_ast());
177 let extension_fns: Vec<&String> = func_names
178 .iter()
179 .filter(|name| {
180 self.registry
181 .get_function(name)
182 .is_some_and(|f| !f.is_standard)
183 })
184 .collect();
185 if !extension_fns.is_empty() {
186 let names = extension_fns
187 .iter()
188 .map(|s| s.as_str())
189 .collect::<Vec<_>>()
190 .join(", ");
191 return ValidationResult {
192 valid: false,
193 error: Some(format!(
194 "Extension function(s) not available in strict mode: {names}. \
195 Only standard JMESPath functions are allowed."
196 )),
197 };
198 }
199 }
200
201 ValidationResult {
202 valid: true,
203 error: None,
204 }
205 }
206 Err(e) => ValidationResult {
207 valid: false,
208 error: Some(e.to_string()),
209 },
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use serde_json::json;
218
219 #[test]
220 fn test_evaluate() {
221 let engine = JpxEngine::new();
222 let input = json!({"users": [{"name": "alice"}, {"name": "bob"}]});
223 let result = engine.evaluate("users[*].name", &input).unwrap();
224 assert_eq!(result, json!(["alice", "bob"]));
225 }
226
227 #[test]
228 fn test_evaluate_str() {
229 let engine = JpxEngine::new();
230 let result = engine.evaluate_str("length(@)", r#"[1, 2, 3]"#).unwrap();
231 assert_eq!(result, json!(3));
232 }
233
234 #[test]
235 fn test_batch_evaluate() {
236 let engine = JpxEngine::new();
237 let input = json!({"a": 1, "b": 2});
238 let exprs = vec!["a".to_string(), "b".to_string(), "c".to_string()];
239 let result = engine.batch_evaluate(&exprs, &input);
240
241 assert_eq!(result.results.len(), 3);
242 assert_eq!(result.results[0].result, Some(json!(1)));
243 assert_eq!(result.results[1].result, Some(json!(2)));
244 assert_eq!(result.results[2].result, Some(json!(null)));
245 }
246
247 #[test]
248 fn test_validate() {
249 let engine = JpxEngine::new();
250
251 let valid = engine.validate("users[*].name");
252 assert!(valid.valid);
253 assert!(valid.error.is_none());
254
255 let invalid = engine.validate("users[*.name");
256 assert!(!invalid.valid);
257 assert!(invalid.error.is_some());
258 }
259
260 #[test]
261 fn test_evaluate_extension_function() {
262 let engine = JpxEngine::new();
263 let result = engine
264 .evaluate("upper(name)", &json!({"name": "alice"}))
265 .unwrap();
266 assert_eq!(result, json!("ALICE"));
267 }
268
269 #[test]
270 fn test_evaluate_strict_rejects_extensions() {
271 let engine = JpxEngine::strict();
272 let result = engine.evaluate("upper(name)", &json!({"name": "alice"}));
273 assert!(result.is_err());
274 assert!(matches!(
275 result,
276 Err(crate::EngineError::EvaluationFailed { .. })
277 ));
278 }
279
280 #[test]
281 fn test_evaluate_invalid_expression() {
282 let engine = JpxEngine::new();
283 let result = engine.evaluate("users[*.name", &json!({}));
284 assert!(matches!(
285 result,
286 Err(crate::EngineError::InvalidExpression(_))
287 ));
288 }
289
290 #[test]
291 fn test_evaluate_str_invalid_json() {
292 let engine = JpxEngine::new();
293 let result = engine.evaluate_str("@", "not json");
294 assert!(matches!(result, Err(crate::EngineError::InvalidJson(_))));
295 }
296
297 #[test]
298 fn test_batch_evaluate_with_errors() {
299 let engine = JpxEngine::new();
300 let exprs = vec!["a".to_string(), "invalid[".to_string()];
301 let result = engine.batch_evaluate(&exprs, &json!({"a": 1}));
302
303 assert_eq!(result.results.len(), 2);
304 assert!(result.results[0].result.is_some());
305 assert!(result.results[0].error.is_none());
306 assert!(result.results[1].result.is_none());
307 assert!(result.results[1].error.is_some());
308 }
309
310 #[test]
311 fn test_evaluate_unicode() {
312 let engine = JpxEngine::new();
313 let input = json!({"name": "ñoño", "greeting": "こんにちは"});
314
315 let result = engine.evaluate("name", &input).unwrap();
316 assert_eq!(result, json!("ñoño"));
317
318 let result = engine.evaluate("greeting", &input).unwrap();
319 assert_eq!(result, json!("こんにちは"));
320 }
321
322 #[test]
323 fn test_evaluate_deeply_nested() {
324 let engine = JpxEngine::new();
325 let input = json!({"a": {"b": {"c": {"d": {"e": "deep"}}}}});
326
327 let result = engine.evaluate("a.b.c.d.e", &input).unwrap();
328 assert_eq!(result, json!("deep"));
329
330 let result = engine.evaluate("a.b.c.d", &input).unwrap();
331 assert_eq!(result, json!({"e": "deep"}));
332 }
333
334 #[test]
335 fn test_evaluate_null_result() {
336 let engine = JpxEngine::new();
337 let input = json!({"a": 1, "b": "hello"});
338
339 let result = engine.evaluate("missing", &input).unwrap();
340 assert_eq!(result, json!(null));
341
342 let result = engine.evaluate("a.b.c", &input).unwrap();
343 assert_eq!(result, json!(null));
344 }
345
346 #[test]
347 fn test_batch_evaluate_large() {
348 let engine = JpxEngine::new();
349 let input = json!({"value": 42});
350 let exprs: Vec<String> = (0..50).map(|_| "value".to_string()).collect();
351
352 let result = engine.batch_evaluate(&exprs, &input);
353 assert_eq!(result.results.len(), 50);
354 for r in &result.results {
355 assert_eq!(r.result, Some(json!(42)));
356 assert!(r.error.is_none());
357 }
358 }
359
360 #[test]
361 fn test_strict_rejects_let_expression() {
362 let engine = JpxEngine::strict();
363 let result = engine.evaluate("let $x = name in $x", &json!({"name": "alice"}));
364 assert!(result.is_err());
365 let err = result.unwrap_err().to_string();
366 assert!(err.contains("strict mode"), "error was: {err}");
367 }
368
369 #[test]
370 fn test_non_strict_allows_let_expression() {
371 let engine = JpxEngine::new();
372 let result = engine
373 .evaluate("let $x = name in $x", &json!({"name": "alice"}))
374 .unwrap();
375 assert_eq!(result, json!("alice"));
376 }
377
378 #[test]
379 fn test_batch_evaluate_empty() {
380 let engine = JpxEngine::new();
381 let input = json!({"a": 1});
382 let exprs: Vec<String> = vec![];
383
384 let result = engine.batch_evaluate(&exprs, &input);
385 assert!(result.results.is_empty());
386 }
387
388 #[test]
389 fn test_validate_complex_valid() {
390 let engine = JpxEngine::new();
391
392 let result = engine.validate("users[?age > `30`].name | sort(@) | join(', ', @)");
393 assert!(result.valid);
394 assert!(result.error.is_none());
395
396 let result = engine.validate("items[*].{id: id, name: name} | [?id > `5`]");
397 assert!(result.valid);
398 assert!(result.error.is_none());
399 }
400
401 #[test]
402 fn test_validate_strict_rejects_let_expression() {
403 let engine = JpxEngine::strict();
404 let result = engine.validate("let $x = name in $x");
405 assert!(!result.valid);
406 let err = result.error.unwrap();
407 assert!(err.contains("strict mode"), "error was: {err}");
408 assert!(err.contains("Let expression"), "error was: {err}");
409 }
410
411 #[test]
412 fn test_validate_strict_rejects_extension_function() {
413 let engine = JpxEngine::strict();
414 let result = engine.validate("upper(name)");
415 assert!(!result.valid);
416 let err = result.error.unwrap();
417 assert!(err.contains("strict mode"), "error was: {err}");
418 assert!(err.contains("upper"), "error was: {err}");
419 }
420
421 #[test]
422 fn test_validate_strict_allows_standard_functions() {
423 let engine = JpxEngine::strict();
424 let result = engine.validate("length(sort(@))");
425 assert!(result.valid, "error: {:?}", result.error);
426 }
427
428 #[test]
429 fn test_validate_non_strict_allows_all() {
430 let engine = JpxEngine::new();
431
432 let result = engine.validate("upper(name)");
433 assert!(result.valid, "error: {:?}", result.error);
434
435 let result = engine.validate("let $x = name in $x");
436 assert!(result.valid, "error: {:?}", result.error);
437 }
438}