1#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
2use std::collections::HashMap;
3
4use bumpalo::Bump;
5
6mod datetime;
7mod errors;
8mod evaluator;
9mod parser;
10
11pub use errors::Error;
12pub use evaluator::functions::FunctionContext;
13pub use evaluator::value::{ArrayFlags, Value};
14
15use evaluator::{frame::Frame, functions::*, Evaluator};
16use parser::ast::Ast;
17
18pub type Result<T> = std::result::Result<T, Error>;
19
20pub struct JsonAta<'a> {
21 ast: Ast,
22 frame: Frame<'a>,
23 arena: &'a Bump,
24}
25
26impl<'a> JsonAta<'a> {
27 pub fn new(expr: &str, arena: &'a Bump) -> Result<JsonAta<'a>> {
28 Ok(Self {
29 ast: parser::parse(expr)?,
30 frame: Frame::new(),
31 arena,
32 })
33 }
34
35 pub fn ast(&self) -> &Ast {
36 &self.ast
37 }
38
39 pub fn assign_var(&self, name: &str, value: &'a Value<'a>) {
40 self.frame.bind(name, value)
41 }
42
43 pub fn register_function(
44 &self,
45 name: &str,
46 arity: usize,
47 implementation: fn(FunctionContext<'a, '_>, &[&'a Value<'a>]) -> Result<&'a Value<'a>>,
48 ) {
49 self.frame.bind(
50 name,
51 Value::nativefn(self.arena, name, arity, implementation),
52 );
53 }
54
55 fn json_value_to_value(&self, json_value: &serde_json::Value) -> &'a mut Value<'a> {
56 match json_value {
57 serde_json::Value::Null => Value::null(self.arena),
58 serde_json::Value::Bool(b) => self.arena.alloc(Value::Bool(*b)),
59 serde_json::Value::Number(n) => Value::number(self.arena, n.as_f64().unwrap()),
60 serde_json::Value::String(s) => Value::string(self.arena, s),
61
62 serde_json::Value::Array(a) => {
63 let array = Value::array_with_capacity(self.arena, a.len(), ArrayFlags::empty());
64 for v in a.iter() {
65 array.push(self.json_value_to_value(v))
66 }
67
68 array
69 }
70 serde_json::Value::Object(o) => {
71 let object = Value::object_with_capacity(self.arena, o.len());
72 for (k, v) in o.iter() {
73 object.insert(k, self.json_value_to_value(v));
74 }
75 object
76 }
77 }
78 }
79
80 pub fn evaluate(
81 &self,
82 input: Option<&str>,
83 bindings: Option<&HashMap<&str, &serde_json::Value>>,
84 ) -> Result<&'a Value<'a>> {
85 if let Some(bindings) = bindings {
86 for (key, json_value) in bindings.iter() {
87 let value = self.json_value_to_value(json_value);
88 self.assign_var(key, value);
89 }
90 };
91
92 self.evaluate_timeboxed(input, None, None)
93 }
94
95 pub fn evaluate_timeboxed(
96 &self,
97 input: Option<&str>,
98 max_depth: Option<usize>,
99 time_limit: Option<usize>,
100 ) -> Result<&'a Value<'a>> {
101 let input = match input {
102 Some(input) => {
103 let input_ast = parser::parse(input)?;
104 let evaluator = Evaluator::new(None, self.arena, None, None);
105 evaluator.evaluate(&input_ast, Value::undefined(), &Frame::new())?
106 }
107 None => Value::undefined(),
108 };
109
110 let input = if input.is_array() {
112 Value::wrap_in_array(self.arena, input, ArrayFlags::WRAPPED)
113 } else {
114 input
115 };
116
117 macro_rules! bind_native {
118 ($name:literal, $arity:literal, $fn:ident) => {
119 self.frame
120 .bind($name, Value::nativefn(&self.arena, $name, $arity, $fn));
121 };
122 }
123
124 self.frame.bind("$", input);
125 bind_native!("abs", 1, fn_abs);
126 bind_native!("append", 2, fn_append);
127 bind_native!("assert", 2, fn_assert);
128 bind_native!("base64decode", 1, fn_base64_decode);
129 bind_native!("base64encode", 1, fn_base64_encode);
130 bind_native!("boolean", 1, fn_boolean);
131 bind_native!("ceil", 1, fn_ceil);
132 bind_native!("contains", 2, fn_contains);
133 bind_native!("count", 1, fn_count);
134 bind_native!("distinct", 1, fn_distinct);
135 bind_native!("each", 2, fn_each);
136 bind_native!("error", 1, fn_error);
137 bind_native!("exists", 1, fn_exists);
138 bind_native!("fromMillis", 3, from_millis);
139 bind_native!("toMillis", 2, to_millis);
140 bind_native!("single", 2, single);
141 bind_native!("filter", 2, fn_filter);
142 bind_native!("floor", 1, fn_floor);
143 bind_native!("join", 2, fn_join);
144 bind_native!("keys", 1, fn_keys);
145 bind_native!("length", 1, fn_length);
146 bind_native!("lookup", 2, fn_lookup);
147 bind_native!("lowercase", 1, fn_lowercase);
148 bind_native!("map", 2, fn_map);
149 bind_native!("match", 2, fn_match);
150 bind_native!("max", 1, fn_max);
151 bind_native!("merge", 1, fn_merge);
152 bind_native!("min", 1, fn_min);
153 bind_native!("not", 1, fn_not);
154 bind_native!("now", 2, fn_now);
155 bind_native!("number", 1, fn_number);
156 bind_native!("pad", 2, fn_pad);
157 bind_native!("power", 2, fn_power);
158 bind_native!("random", 0, fn_random);
159 bind_native!("reduce", 3, fn_reduce);
160 bind_native!("replace", 4, fn_replace);
161 bind_native!("reverse", 1, fn_reverse);
162 bind_native!("round", 2, fn_round);
163 bind_native!("sort", 2, fn_sort);
164 bind_native!("split", 3, fn_split);
165 bind_native!("sqrt", 1, fn_sqrt);
166 bind_native!("string", 1, fn_string);
167 bind_native!("substring", 3, fn_substring);
168 bind_native!("substringBefore", 2, fn_substring_before);
169 bind_native!("substringAfter", 2, fn_substring_after);
170 bind_native!("sum", 1, fn_sum);
171 bind_native!("trim", 1, fn_trim);
172 bind_native!("uppercase", 1, fn_uppercase);
173 bind_native!("zip", 1, fn_zip);
174 bind_native!("millis", 0, fn_millis);
175 bind_native!("uuid", 0, fn_uuid);
176
177 let chain_ast = Some(parser::parse(
178 "function($f, $g) { function($x){ $g($f($x)) } }",
179 )?);
180 let evaluator = Evaluator::new(chain_ast, self.arena, max_depth, time_limit);
181 evaluator.evaluate(&self.ast, input, &self.frame)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use chrono::{DateTime, Datelike, Local, Offset};
188 use regress::Regex;
189
190 use bumpalo::collections::String as BumpString;
191
192 use super::*;
193
194 #[test]
195 fn register_function_simple() {
196 let arena = Bump::new();
197 let jsonata = JsonAta::new("$test()", &arena).unwrap();
198 jsonata.register_function("test", 0, |ctx, _| Ok(Value::number(ctx.arena, 1)));
199
200 let result = jsonata.evaluate(Some(r#"anything"#), None);
201
202 assert_eq!(result.unwrap(), Value::number(&arena, 1));
203 }
204
205 #[test]
206 fn register_function_override_now() {
207 let arena = Bump::new();
208 let jsonata = JsonAta::new("$now()", &arena).unwrap();
209 jsonata.register_function("now", 0, |ctx, _| {
210 Ok(Value::string(ctx.arena, "time for tea"))
211 });
212
213 let result = jsonata.evaluate(None, None);
214
215 assert_ne!(result.unwrap().as_str(), "time for tea");
216 }
217
218 #[test]
219 fn register_function_map_squareroot() {
220 let arena = Bump::new();
221 let jsonata = JsonAta::new("$map([1,4,9,16], $squareroot)", &arena).unwrap();
222 jsonata.register_function("squareroot", 1, |ctx, args| {
223 let num = &args[0];
224 Ok(Value::number(ctx.arena, (num.as_f64()).sqrt()))
225 });
226
227 let result = jsonata.evaluate(Some(r#"anything"#), None);
228
229 assert_eq!(
230 result
231 .unwrap()
232 .members()
233 .map(|v| v.as_f64())
234 .collect::<Vec<f64>>(),
235 vec![1.0, 2.0, 3.0, 4.0]
236 );
237 }
238
239 #[test]
240 fn register_function_filter_even() {
241 let arena = Bump::new();
242 let jsonata = JsonAta::new("$filter([1,4,9,16], $even)", &arena).unwrap();
243 jsonata.register_function("even", 1, |_ctx, args| {
244 let num = &args[0];
245 Ok(Value::bool((num.as_f64()) % 2.0 == 0.0))
246 });
247
248 let result = jsonata.evaluate(Some(r#"anything"#), None);
249
250 assert_eq!(
251 result
252 .unwrap()
253 .members()
254 .map(|v| v.as_f64())
255 .collect::<Vec<f64>>(),
256 vec![4.0, 16.0]
257 );
258 }
259
260 #[test]
261 fn evaluate_with_bindings_simple() {
262 let arena = Bump::new();
263 let jsonata = JsonAta::new("$a + $b", &arena).unwrap();
264
265 let a = &serde_json::Value::Number(serde_json::Number::from(1));
266 let b = &serde_json::Value::Number(serde_json::Number::from(2));
267
268 let mut bindings = HashMap::new();
269 bindings.insert("a", a);
270 bindings.insert("b", b);
271
272 let result = jsonata.evaluate(None, Some(&bindings));
273
274 assert_eq!(result.unwrap().as_f64(), 3.0);
275 }
276
277 #[test]
278 fn evaluate_with_bindings_nested() {
279 let arena = Bump::new();
280 let jsonata = JsonAta::new("$foo[0].a + $foo[1].b", &arena).unwrap();
281
282 let foo_string = r#"
283 [
284 {
285 "a": 1
286 },
287 {
288 "b": 2
289 }
290 ]
291 "#;
292
293 let foo: serde_json::Value = serde_json::from_str(foo_string).unwrap();
294
295 let mut bindings = HashMap::new();
296 bindings.insert("foo", &foo);
297
298 let result = jsonata.evaluate(None, Some(&bindings));
299
300 assert_eq!(result.unwrap().as_f64(), 3.0);
301 }
302
303 #[test]
304 fn evaluate_with_random() {
305 let arena = Bump::new();
306 let jsonata = JsonAta::new("$random()", &arena).unwrap();
307
308 let result = jsonata.evaluate(None, None).unwrap();
309
310 assert!(result.as_f64() >= 0.0);
311 assert!(result.as_f64() < 1.0);
312 }
313
314 #[test]
315 fn test_now_default_utc() {
316 let arena = Bump::new();
317 let jsonata = JsonAta::new("$now()", &arena).unwrap();
318 let result = jsonata.evaluate(None, None).unwrap();
319 let result_str = result.as_str();
320
321 println!("test_now_default_utc {}", result_str);
322
323 let parsed_result = DateTime::parse_from_rfc3339(&result_str)
325 .expect("Should parse valid ISO 8601 timestamp");
326
327 assert_eq!(
328 parsed_result.offset().fix().local_minus_utc(),
329 0,
330 "Should be UTC"
331 );
332 }
333
334 #[test]
335 fn test_now_with_valid_timezone_and_format() {
336 let arena = Bump::new();
337 let jsonata = JsonAta::new(
338 "$now('[M01]/[D01]/[Y0001] [h#1]:[m01][P] [z]', '-0500')",
339 &arena,
340 )
341 .unwrap();
342 let result = jsonata.evaluate(None, None).unwrap();
343 let result_str = result.as_str();
344
345 println!("test_now_with_valid_timezone_and_format {}", result_str);
346
347 let expected_format =
349 Regex::new(r"^\d{2}/\d{2}/\d{4} \d{1,2}:\d{2}(AM|PM|am|pm) GMT-05:00$").unwrap();
350
351 let is_match = expected_format.find_iter(&result_str).next().is_some();
353 assert!(
354 is_match,
355 "Expected custom formatted time with timezone, got: {}",
356 result_str
357 );
358 }
359
360 #[test]
361 fn test_now_with_valid_format_but_no_timezone() {
362 let arena = Bump::new();
363 let jsonata = JsonAta::new("$now('[M01]/[D01]/[Y0001] [h#1]:[m01][P]')", &arena).unwrap();
364 let result = jsonata.evaluate(None, None).unwrap();
365 let result_str = result.as_str();
366
367 let expected_format =
368 Regex::new(r"^\d{2}/\d{2}/\d{4} \d{1,2}:\d{2}(AM|PM|am|pm)$").unwrap();
369
370 let is_match = expected_format.find_iter(&result_str).next().is_some();
372 assert!(
373 is_match,
374 "Expected custom formatted time without timezone, got: {}",
375 result_str
376 );
377 }
378
379 #[test]
380 fn test_now_with_invalid_timezone() {
381 let arena = Bump::new();
382 let jsonata = JsonAta::new("$now('', 'invalid')", &arena).unwrap();
383 let result = jsonata.evaluate(None, None);
384
385 assert!(
387 result.is_err(),
388 "Expected a runtime error for invalid timezone"
389 );
390
391 if let Err(err) = result {
393 assert_eq!(
395 err.to_string(),
396 "T0410 @ 2: Argument 1 of function now does not match function signature"
397 );
398 }
399 }
400
401 #[test]
402 fn test_now_with_valid_timezone_but_no_format() {
403 let arena = Bump::new();
404 let jsonata = JsonAta::new("$now('', '-0500')", &arena).unwrap();
405 let result = jsonata.evaluate(None, None).unwrap();
406 let result_str = result.as_str();
407
408 println!("test_now_with_valid_timezone_but_no_format {}", result_str);
409
410 assert!(
412 result_str.is_empty(),
413 "Expected empty string for valid timezone but no format"
414 );
415 }
416
417 #[test]
418 fn test_now_with_too_many_arguments() {
419 let arena = Bump::new();
420
421 let jsonata = JsonAta::new("$now('', '-0500', 'extra')", &arena).unwrap();
423 let result = jsonata.evaluate(None, None);
424
425 assert!(
427 result.is_err(),
428 "Expected an error due to too many arguments, but got a result"
429 );
430
431 if let Err(e) = result {
432 assert!(
434 matches!(e, Error::T0410ArgumentNotValid { .. }),
435 "Expected TooManyArguments error, but got: {:?}",
436 e
437 );
438 }
439 }
440
441 #[test]
442 fn test_now_with_edge_case_timezones() {
443 let arena = Bump::new();
444
445 let jsonata = JsonAta::new("$now('[H01]:[m01] [z]', '+1440')", &arena).unwrap();
447 let result = jsonata.evaluate(None, None).unwrap();
448 let result_str = result.as_str();
449 println!("test_now_with_extreme_positive_timezone {:?}", result_str);
450 assert!(result_str.contains("GMT+14:40"), "Expected GMT+14:40");
451
452 let jsonata_minimal = JsonAta::new("$now('[H01]:[m01] [z]', '-0000')", &arena).unwrap();
454 let result_minimal = jsonata_minimal.evaluate(None, None).unwrap();
455 let result_str_minimal = result_minimal.as_str();
456 println!("test_now_with_minimal_timezone {:?}", result_str_minimal);
457
458 assert!(
460 result_str_minimal.contains("GMT+00:00"),
461 "Expected GMT+00:00"
462 );
463 }
464
465 #[test]
466 fn test_custom_format_with_components() {
467 let arena = Bump::new();
468
469 let jsonata = JsonAta::new(
471 "$now('[M01]/[D01]/[Y0001] [h#1]:[m01][P] [z]', '-0500')",
472 &arena,
473 )
474 .unwrap();
475 let result = jsonata.evaluate(None, None).unwrap();
476 let result_str = result.as_str();
477
478 println!("Formatted date: {}", result_str);
479
480 let expected_format =
482 Regex::new(r"^\d{2}/\d{2}/\d{4} \d{1,2}:\d{2}(am|pm|AM|PM) GMT-05:00$").unwrap();
483
484 let is_match = expected_format.find_iter(&result_str).next().is_some();
486 assert!(
487 is_match,
488 "Expected 12-hour format with timezone, got: {}",
489 result_str
490 );
491 }
492
493 #[test]
494 fn test_now_with_invalid_timezone_should_fail() {
495 let arena = Bump::new();
496 let jsonata = JsonAta::new("$now('', 'invalid')", &arena).unwrap();
497 let result = jsonata.evaluate(None, None);
498
499 assert!(
500 result.is_err(),
501 "Expected a runtime error for invalid timezone"
502 );
503
504 let error = result.unwrap_err();
505 assert_eq!(
506 error.to_string(),
507 "T0410 @ 2: Argument 1 of function now does not match function signature"
508 );
509 }
510
511 #[test]
512 fn test_now_invalid_timezone_with_format() {
513 let arena = Bump::new();
514 let jsonata = JsonAta::new("$now('[h]:[m]:[s]', 'xx')", &arena).unwrap();
515 let result = jsonata.evaluate(None, None);
516
517 assert!(
518 result.is_err(),
519 "Expected a runtime error for invalid timezone"
520 );
521 if let Err(err) = result {
522 assert_eq!(
523 err.to_string(),
524 "T0410 @ 2: Argument 1 of function now does not match function signature"
525 );
526 }
527 }
528
529 #[test]
530 fn test_now_invalid_format_with_timezone() {
531 let arena = Bump::new();
532 let jsonata = JsonAta::new("$now('[h01]:[m01]:[s01]', 'xx')", &arena).unwrap();
533 let result = jsonata.evaluate(None, None);
534
535 assert!(
536 result.is_err(),
537 "Expected a runtime error for invalid format"
538 );
539 if let Err(err) = result {
540 assert_eq!(
541 err.to_string(),
542 "T0410 @ 2: Argument 1 of function now does not match function signature"
543 );
544 }
545 }
546
547 #[test]
548 fn test_from_millis_with_custom_format() {
549 let arena = Bump::new();
550
551 let jsonata = JsonAta::new(
553 "$fromMillis(1726148700000, '[M01]/[D01]/[Y0001] [H01]:[m01]:[s01] [z]')",
554 &arena,
555 )
556 .unwrap();
557
558 let result = jsonata.evaluate(None, None).unwrap();
560 let result_str = result.as_str();
561
562 println!("Formatted date: {}", result_str);
563
564 let expected_format =
566 Regex::new(r"^\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2} GMT\+\d{2}:\d{2}$").unwrap();
567
568 let is_match = expected_format.find_iter(&result_str).next().is_some();
570 assert!(
571 is_match,
572 "Expected custom formatted date with timezone, got: {}",
573 result_str
574 );
575 }
576
577 #[test]
578 fn test_to_millis_with_custom_format() {
579 let arena = Bump::new();
580
581 let jsonata = JsonAta::new(
583 "$toMillis('13/09/2024 13:45:00', '[D01]/[M01]/[Y0001] [H01]:[m01]:[s01]')",
584 &arena,
585 )
586 .unwrap();
587
588 let result = jsonata.evaluate(None, None);
590
591 match result {
592 Ok(value) => {
593 if value.is_number() {
594 let millis = value.as_f64();
595
596 assert_eq!(
598 millis, 1726235100000.0,
599 "Expected milliseconds for the given date"
600 );
601 } else {
602 println!("Result is not a number: {:?}", value);
603 panic!("Expected a number, but got something else.");
604 }
605 }
606 Err(err) => {
607 println!("Evaluation error: {:?}", err);
608 panic!("Failed to evaluate the expression.");
609 }
610 }
611 }
612
613 #[test]
614 fn evaluate_with_reduce() {
615 let arena = Bump::new(); let jsonata = JsonAta::new(
617 "$reduce([1..5], function($i, $j){$i * $j})", &arena,
619 )
620 .unwrap();
621
622 let result = jsonata.evaluate(None, None).unwrap(); assert_eq!(result.as_f64(), 120.0);
626 }
627
628 #[test]
629 fn evaluate_with_reduce_sum() {
630 let arena = Bump::new();
631 let jsonata = JsonAta::new(
632 "$reduce([1..5], function($i, $j){$i + $j})", &arena,
634 )
635 .unwrap();
636
637 let result = jsonata.evaluate(None, None).unwrap();
638
639 assert_eq!(result.as_f64(), 15.0);
641 }
642
643 #[test]
644 fn evaluate_with_reduce_custom_initial_value() {
645 let arena = Bump::new();
646 let jsonata = JsonAta::new(
647 "$reduce([1..5], function($i, $j){$i * $j}, 10)", &arena,
649 )
650 .unwrap();
651
652 let result = jsonata.evaluate(None, None).unwrap();
653
654 assert_eq!(result.as_f64(), 1200.0);
656 }
657
658 #[test]
659 fn evaluate_with_reduce_empty_array() {
660 let arena = Bump::new();
661 let jsonata = JsonAta::new(
662 "$reduce([], function($i, $j){$i + $j})", &arena,
664 )
665 .unwrap();
666
667 let result = jsonata.evaluate(None, None).unwrap();
668
669 assert!(result.is_undefined());
671 }
672
673 #[test]
674 fn evaluate_with_reduce_single_element() {
675 let arena = Bump::new();
676 let jsonata = JsonAta::new(
677 "$reduce([42], function($i, $j){$i + $j})", &arena,
679 )
680 .unwrap();
681
682 let result = jsonata.evaluate(None, None).unwrap();
683
684 assert_eq!(result.as_f64(), 42.0);
686 }
687
688 #[test]
689 fn evaluate_with_reduce_subtraction() {
690 let arena = Bump::new();
691 let jsonata = JsonAta::new(
692 "$reduce([10, 3, 2], function($i, $j){$i - $j})", &arena,
694 )
695 .unwrap();
696
697 let result = jsonata.evaluate(None, None).unwrap();
698
699 assert_eq!(result.as_f64(), 5.0);
701 }
702
703 #[test]
704 fn evaluate_with_reduce_initial_value_greater() {
705 let arena = Bump::new();
706 let jsonata = JsonAta::new(
707 "$reduce([1..3], function($i, $j){$i - $j}, 10)", &arena,
709 )
710 .unwrap();
711
712 let result = jsonata.evaluate(None, None).unwrap();
713
714 assert_eq!(result.as_f64(), 4.0);
716 }
717
718 #[test]
719 fn test_match_regex_with_jsonata() {
720 let arena = Bump::new();
721
722 let jsonata = JsonAta::new(r#"$match("123456789", /^[0-9]{9}$/)"#, &arena).unwrap();
724 let result = jsonata.evaluate(None, None).unwrap();
725
726 let match_value: &Value = arena.alloc(Value::string(&arena, "123456789"));
728 let index_value: &Value = arena.alloc(Value::number(&arena, 0.0));
729 let groups_array: &Value = &*arena.alloc(Value::Array(
730 bumpalo::collections::Vec::new_in(&arena),
731 ArrayFlags::empty(),
732 ));
733
734 let mut match_obj = hashbrown::HashMap::with_capacity_in(3, &arena);
735 match_obj.insert(BumpString::from_str_in("match", &arena), match_value);
736 match_obj.insert(BumpString::from_str_in("index", &arena), index_value);
737 match_obj.insert(BumpString::from_str_in("groups", &arena), groups_array);
738
739 let expected_match: &Value = &*arena.alloc(Value::Object(match_obj));
740
741 assert_eq!(
742 result,
743 &*arena.alloc(Value::Array(
744 bumpalo::collections::Vec::from_iter_in([expected_match], &arena),
745 ArrayFlags::empty()
746 ))
747 );
748
749 let jsonata_invalid =
751 JsonAta::new(r#"$match("12345-6789", /^[0-9]{9}$/)"#, &arena).unwrap();
752 let result_invalid = jsonata_invalid.evaluate(None, None).unwrap();
753
754 let empty_array: &Value = &*arena.alloc(Value::Array(
756 bumpalo::collections::Vec::new_in(&arena), ArrayFlags::empty(),
758 ));
759 assert_eq!(result_invalid, empty_array);
760 }
761
762 #[test]
763 fn evaluate_expect_type_errors() {
764 for expr in [
765 "$fromMillis('foo')",
766 "$fromMillis(1, 1)",
767 "$fromMillis(1, '', 1)",
768 "$toMillis(1)",
769 "$toMillis('1970-01-01T00:00:00.000Z', 1)",
770 ] {
771 let arena = Bump::new();
772 let jsonata = JsonAta::new(expr, &arena).unwrap();
773 let err = jsonata.evaluate(None, None).unwrap_err();
774
775 assert_eq!(err.code(), "T0410", "Expected type error from {expr}");
776 }
777 }
778
779 #[test]
780 fn evaluate_millis_returns_number() {
781 let arena = Bump::new();
782 let jsonata = JsonAta::new("$millis()", &arena).unwrap();
783 let result = jsonata.evaluate(None, None).unwrap();
784
785 assert!(result.is_number());
786 }
787
788 #[test]
789 fn test_from_millis_formats_date() {
790 let arena = Bump::new();
792
793 let jsonata = JsonAta::new("$fromMillis($millis(),'[Y01][M01][D01]')", &arena).unwrap();
795
796 let result = jsonata.evaluate(None, None).unwrap();
798
799 let now = Local::now();
801 let expected = format!(
802 "{:02}{:02}{:02}",
803 now.year() % 100, now.month(),
805 now.day()
806 );
807
808 let result_string = result.to_string();
810 let actual = result_string.trim_matches('"'); assert_eq!(actual, expected);
814 }
815}