1pub mod ast;
33pub mod ast_transform;
34mod compiler;
35mod datetime;
36pub mod evaluator;
37pub mod functions;
38pub mod parser;
39mod signature;
40pub mod value;
41mod vm;
42
43#[cfg(feature = "bench")]
53pub mod _bench {
54 use crate::ast::AstNode;
55 pub use crate::evaluator::EvaluatorError;
56 use crate::value::JValue;
57
58 pub struct CompiledProgram(crate::vm::BytecodeProgram);
60
61 pub fn compile(ast: &AstNode) -> Option<CompiledProgram> {
67 crate::evaluator::try_compile_expr(ast)
68 .map(|ce| CompiledProgram(crate::compiler::BytecodeCompiler::compile(&ce)))
69 }
70
71 pub fn run(prog: &CompiledProgram, data: &JValue) -> Result<JValue, EvaluatorError> {
73 crate::vm::Vm::with_options(&prog.0, crate::evaluator::EvaluatorOptions::default())
74 .run(data, None)
75 }
76}
77
78const JSONATA_REFERENCE_VERSION: &str = "2.1.0";
82
83#[cfg(feature = "python")]
84use crate::value::JValue;
85#[cfg(feature = "python")]
86use indexmap::IndexMap;
87#[cfg(feature = "python")]
88use pyo3::exceptions::{PyTypeError, PyValueError};
89#[cfg(feature = "python")]
90use pyo3::prelude::*;
91#[cfg(feature = "python")]
92use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyList, PyString};
93
94#[cfg(feature = "python")]
109#[pyclass(unsendable)]
110struct JsonataData {
111 data: JValue,
112}
113
114#[cfg(feature = "python")]
115#[pymethods]
116impl JsonataData {
117 #[new]
119 fn new(py: Python, data: Py<PyAny>) -> PyResult<Self> {
120 let jvalue = python_to_json(py, &data)?;
121 Ok(JsonataData { data: jvalue })
122 }
123
124 #[staticmethod]
126 fn from_json(json_str: &str) -> PyResult<Self> {
127 let data = JValue::from_json_str(json_str)
128 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
129 Ok(JsonataData { data })
130 }
131}
132
133#[cfg(feature = "python")]
154#[pyclass(unsendable)]
155struct JsonataExpression {
156 ast: ast::AstNode,
158 bytecode: std::cell::OnceCell<Option<vm::BytecodeProgram>>,
162 default_options: evaluator::EvaluatorOptions,
166}
167
168#[cfg(feature = "python")]
169impl JsonataExpression {
170 fn run_eval(
173 &self,
174 py: Python,
175 data: &JValue,
176 bindings: Option<Py<PyAny>>,
177 options: evaluator::EvaluatorOptions,
178 ) -> PyResult<JValue> {
179 if bindings.is_none() {
180 let bytecode = self.bytecode.get_or_init(|| {
181 evaluator::try_compile_expr(&self.ast)
182 .map(|ce| compiler::BytecodeCompiler::compile(&ce))
183 });
184 if let Some(bc) = bytecode {
185 vm::Vm::with_options(bc, options.clone())
186 .run(data, None)
187 .map_err(evaluator_error_to_py)
188 } else {
189 let mut ev = evaluator::Evaluator::with_options(evaluator::Context::new(), options);
190 ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
191 }
192 } else {
193 let mut ev = create_evaluator(py, bindings, options)?;
194 ev.evaluate(&self.ast, data).map_err(evaluator_error_to_py)
195 }
196 }
197}
198
199#[cfg(feature = "python")]
200#[pymethods]
201impl JsonataExpression {
202 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
204 #[allow(clippy::too_many_arguments)]
205 fn evaluate(
206 &self,
207 py: Python,
208 data: Py<PyAny>,
209 bindings: Option<Py<PyAny>>,
210 timeout: Option<u64>,
211 max_stack_depth: Option<usize>,
212 max_sequence_length: Option<usize>,
213 ) -> PyResult<Py<PyAny>> {
214 let json_data = python_to_json(py, &data)?;
215 let options = evaluator::EvaluatorOptions {
216 timeout_ms: timeout.or(self.default_options.timeout_ms),
217 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
218 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
219 };
220 json_to_python(py, &self.run_eval(py, &json_data, bindings, options)?)
221 }
222
223 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
234 #[allow(clippy::too_many_arguments)]
235 fn evaluate_with_data(
236 &self,
237 py: Python,
238 data: &JsonataData,
239 bindings: Option<Py<PyAny>>,
240 timeout: Option<u64>,
241 max_stack_depth: Option<usize>,
242 max_sequence_length: Option<usize>,
243 ) -> PyResult<Py<PyAny>> {
244 let options = evaluator::EvaluatorOptions {
245 timeout_ms: timeout.or(self.default_options.timeout_ms),
246 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
247 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
248 };
249 json_to_python(py, &self.run_eval(py, &data.data, bindings, options)?)
250 }
251
252 #[pyo3(signature = (data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
263 #[allow(clippy::too_many_arguments)]
264 fn evaluate_data_to_json(
265 &self,
266 py: Python,
267 data: &JsonataData,
268 bindings: Option<Py<PyAny>>,
269 timeout: Option<u64>,
270 max_stack_depth: Option<usize>,
271 max_sequence_length: Option<usize>,
272 ) -> PyResult<String> {
273 let options = evaluator::EvaluatorOptions {
274 timeout_ms: timeout.or(self.default_options.timeout_ms),
275 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
276 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
277 };
278 self.run_eval(py, &data.data, bindings, options)?
279 .to_json_string()
280 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
281 }
282
283 #[pyo3(signature = (json_str, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
301 #[allow(clippy::too_many_arguments)]
302 fn evaluate_json(
303 &self,
304 py: Python,
305 json_str: &str,
306 bindings: Option<Py<PyAny>>,
307 timeout: Option<u64>,
308 max_stack_depth: Option<usize>,
309 max_sequence_length: Option<usize>,
310 ) -> PyResult<String> {
311 let json_data = JValue::from_json_str(json_str)
312 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
313 let options = evaluator::EvaluatorOptions {
314 timeout_ms: timeout.or(self.default_options.timeout_ms),
315 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
316 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
317 };
318 self.run_eval(py, &json_data, bindings, options)?
319 .to_json_string()
320 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
321 }
322
323 #[pyo3(signature = (json_str, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
340 #[allow(clippy::too_many_arguments)]
341 fn evaluate_json_or_none(
342 &self,
343 py: Python,
344 json_str: Option<&str>,
345 bindings: Option<Py<PyAny>>,
346 timeout: Option<u64>,
347 max_stack_depth: Option<usize>,
348 max_sequence_length: Option<usize>,
349 ) -> PyResult<Option<String>> {
350 let json_data = match json_str {
351 Some(s) => JValue::from_json_str(s)
352 .map_err(|e| PyValueError::new_err(format!("Invalid JSON: {}", e)))?,
353 None => JValue::Undefined,
354 };
355 let options = evaluator::EvaluatorOptions {
356 timeout_ms: timeout.or(self.default_options.timeout_ms),
357 max_stack_depth: max_stack_depth.or(self.default_options.max_stack_depth),
358 max_sequence_length: max_sequence_length.or(self.default_options.max_sequence_length),
359 };
360 let result = self.run_eval(py, &json_data, bindings, options)?;
361 if result.is_undefined() {
362 return Ok(None);
363 }
364 result
365 .to_json_string()
366 .map(Some)
367 .map_err(|e| PyValueError::new_err(format!("Failed to serialize result: {}", e)))
368 }
369}
370
371#[cfg(feature = "python")]
395#[pyfunction]
396#[pyo3(signature = (expression, timeout=None, max_stack_depth=None, max_sequence_length=None))]
397fn compile(
398 expression: &str,
399 timeout: Option<u64>,
400 max_stack_depth: Option<usize>,
401 max_sequence_length: Option<usize>,
402) -> PyResult<JsonataExpression> {
403 let ast = parser::parse(expression).map_err(parser_error_to_py)?;
404
405 Ok(JsonataExpression {
406 ast,
407 bytecode: std::cell::OnceCell::new(),
408 default_options: build_evaluator_options(timeout, max_stack_depth, max_sequence_length),
409 })
410}
411
412#[cfg(feature = "python")]
440#[pyfunction]
441#[pyo3(signature = (expression, data, bindings=None, timeout=None, max_stack_depth=None, max_sequence_length=None))]
442#[allow(clippy::too_many_arguments)]
443fn evaluate(
444 py: Python,
445 expression: &str,
446 data: Py<PyAny>,
447 bindings: Option<Py<PyAny>>,
448 timeout: Option<u64>,
449 max_stack_depth: Option<usize>,
450 max_sequence_length: Option<usize>,
451) -> PyResult<Py<PyAny>> {
452 let expr = compile(expression, None, None, None)?;
453 expr.evaluate(
454 py,
455 data,
456 bindings,
457 timeout,
458 max_stack_depth,
459 max_sequence_length,
460 )
461}
462
463#[cfg(feature = "python")]
473fn python_to_json(py: Python, obj: &Py<PyAny>) -> PyResult<JValue> {
474 python_to_json_bound(obj.bind(py))
475}
476
477#[cfg(feature = "python")]
483fn python_to_json_bound(obj: &Bound<'_, PyAny>) -> PyResult<JValue> {
484 if obj.is_none() {
485 return Ok(JValue::Null);
486 }
487
488 if obj.is_instance_of::<PyBool>() {
490 return Ok(JValue::Bool(obj.extract::<bool>()?));
491 }
492 if obj.is_instance_of::<PyInt>() {
493 return Ok(JValue::Number(obj.extract::<i64>()? as f64));
494 }
495 if obj.is_instance_of::<PyFloat>() {
496 return Ok(JValue::Number(obj.extract::<f64>()?));
497 }
498 if obj.is_instance_of::<PyString>() {
499 return Ok(JValue::string(obj.extract::<String>()?));
500 }
501 if let Ok(list) = obj.cast::<PyList>() {
502 let mut result = Vec::with_capacity(list.len());
503 for item in list.iter() {
504 result.push(python_to_json_bound(&item)?);
505 }
506 return Ok(JValue::array(result));
507 }
508 if let Ok(dict) = obj.cast::<PyDict>() {
509 let mut result = IndexMap::with_capacity(dict.len());
510 for (key, value) in dict.iter() {
511 let key_str = key.extract::<String>()?;
512 result.insert(key_str, python_to_json_bound(&value)?);
513 }
514 return Ok(JValue::object(result));
515 }
516
517 if let Ok(b) = obj.extract::<bool>() {
519 return Ok(JValue::Bool(b));
520 }
521 if let Ok(i) = obj.extract::<i64>() {
522 return Ok(JValue::Number(i as f64));
523 }
524 if let Ok(f) = obj.extract::<f64>() {
525 return Ok(JValue::Number(f));
526 }
527 if let Ok(s) = obj.extract::<String>() {
528 return Ok(JValue::string(s));
529 }
530
531 Err(PyTypeError::new_err(format!(
532 "Cannot convert Python object to JSON: {}",
533 obj.get_type().name()?
534 )))
535}
536
537#[cfg(feature = "python")]
548fn json_to_python(py: Python, value: &JValue) -> PyResult<Py<PyAny>> {
549 match value {
550 JValue::Null | JValue::Undefined => Ok(py.None()),
551
552 JValue::Bool(b) => Ok(b.into_pyobject(py).unwrap().to_owned().into_any().unbind()),
553
554 JValue::Number(n) => {
555 if n.fract() == 0.0 && n.is_finite() && *n >= i64::MIN as f64 && *n <= i64::MAX as f64 {
557 Ok((*n as i64).into_pyobject(py).unwrap().into_any().unbind())
558 } else {
559 Ok(n.into_pyobject(py).unwrap().into_any().unbind())
560 }
561 }
562
563 JValue::String(s) => Ok((&**s).into_pyobject(py).unwrap().into_any().unbind()),
564
565 JValue::Array(arr) => {
566 let all_objects =
569 arr.len() >= 2 && arr.iter().all(|item| matches!(item, JValue::Object(_)));
570 if all_objects {
571 let first_obj = match arr.first() {
572 Some(JValue::Object(obj)) => obj,
573 _ => unreachable!("all_objects guard ensures first element is an object"),
574 };
575
576 let interned_keys: Vec<(&str, Py<PyString>)> = first_obj
579 .keys()
580 .map(|k| (k.as_str(), PyString::new(py, k).unbind()))
581 .collect();
582
583 let items: Vec<Py<PyAny>> = arr
584 .iter()
585 .map(|item| {
586 let obj = match item {
588 JValue::Object(obj) => obj,
589 _ => unreachable!(),
590 };
591 let dict = PyDict::new(py);
592 for (key_str, py_key) in &interned_keys {
593 if let Some(value) = obj.get(*key_str) {
594 dict.set_item(py_key.bind(py), json_to_python(py, value)?)?;
595 }
596 }
597 for (key, value) in obj.iter() {
599 if !first_obj.contains_key(key) {
600 dict.set_item(key, json_to_python(py, value)?)?;
601 }
602 }
603 Ok(dict.unbind().into())
604 })
605 .collect::<PyResult<Vec<_>>>()?;
606 return Ok(PyList::new(py, &items)?.unbind().into());
607 }
608
609 let items: Vec<Py<PyAny>> = arr
611 .iter()
612 .map(|item| json_to_python(py, item))
613 .collect::<PyResult<Vec<_>>>()?;
614 Ok(PyList::new(py, &items)?.unbind().into())
615 }
616
617 JValue::Object(obj) => {
618 let dict = PyDict::new(py);
619 for (key, value) in obj.iter() {
620 dict.set_item(key, json_to_python(py, value)?)?;
621 }
622 Ok(dict.unbind().into())
623 }
624
625 JValue::Lambda { .. } | JValue::Builtin { .. } | JValue::Regex { .. } => Ok(py.None()),
626 }
627}
628
629#[cfg(feature = "python")]
632fn build_evaluator_options(
633 timeout: Option<u64>,
634 max_stack_depth: Option<usize>,
635 max_sequence_length: Option<usize>,
636) -> evaluator::EvaluatorOptions {
637 evaluator::EvaluatorOptions {
638 timeout_ms: timeout,
639 max_stack_depth,
640 max_sequence_length,
641 }
642}
643
644#[cfg(feature = "python")]
646fn create_evaluator(
647 py: Python,
648 bindings: Option<Py<PyAny>>,
649 options: evaluator::EvaluatorOptions,
650) -> PyResult<evaluator::Evaluator> {
651 let mut context = evaluator::Context::new();
652 if let Some(bindings_obj) = bindings {
653 let bindings_json = python_to_json(py, &bindings_obj)?;
654 if let JValue::Object(map) = bindings_json {
655 for (key, value) in map.iter() {
656 context.bind(key.clone(), value.clone());
657 }
658 } else {
659 return Err(PyTypeError::new_err("bindings must be a dictionary"));
660 }
661 }
662 Ok(evaluator::Evaluator::with_options(context, options))
663}
664
665#[cfg(feature = "python")]
667fn evaluator_error_to_py(e: evaluator::EvaluatorError) -> PyErr {
668 PyValueError::new_err(e.message().to_string())
669}
670
671#[cfg(feature = "python")]
673fn parser_error_to_py(e: parser::ParserError) -> PyErr {
674 PyValueError::new_err(e.display_message())
675}
676
677#[cfg(feature = "python")]
679#[pymodule]
680fn _jsonatapy(m: &Bound<'_, PyModule>) -> PyResult<()> {
681 m.add_function(wrap_pyfunction!(compile, m)?)?;
682 m.add_function(wrap_pyfunction!(evaluate, m)?)?;
683 m.add_class::<JsonataExpression>()?;
684 m.add_class::<JsonataData>()?;
685
686 m.add("__version__", env!("CARGO_PKG_VERSION"))?;
688 m.add("__jsonata_version__", JSONATA_REFERENCE_VERSION)?;
689
690 Ok(())
691}
692
693#[cfg(test)]
694mod tests {
695 #[test]
696 fn test_module_creation() {
697 assert!(!env!("CARGO_PKG_VERSION").is_empty());
699 }
700
701 mod parser_error_handling {
702 use super::super::*;
703
704 #[test]
713 fn test_parser_error_to_py_coded_error_no_prefix() {
714 let coded_error = parser::ParserError::Coded {
716 code: "S0214",
717 message: "Expected a variable reference after @".to_string(),
718 };
719 let msg = coded_error.display_message();
720
721 assert!(
723 msg.starts_with("S0214:"),
724 "Expected message to start with 'S0214:', got: {}",
725 msg
726 );
727 assert!(
728 !msg.starts_with("Parse error:"),
729 "Expected no 'Parse error:' prefix, got: {}",
730 msg
731 );
732 }
733
734 #[test]
735 fn test_parser_error_to_py_non_coded_error_with_prefix() {
736 let non_coded_error = parser::ParserError::UnexpectedToken("foo".to_string());
738 let msg = non_coded_error.display_message();
739
740 assert!(
742 msg.starts_with("Parse error:"),
743 "Expected message to start with 'Parse error:', got: {}",
744 msg
745 );
746 }
747 }
748}