1use crate::context::Context;
2use crate::magic::{Arguments, Identifier, This};
3use crate::objects::{Value, ValueType};
4use crate::resolvers::{Argument, Resolver};
5use crate::ExecutionError;
6use cel_parser::Expression;
7use std::cmp::Ordering;
8use std::convert::TryInto;
9use std::sync::Arc;
10
11type Result<T> = std::result::Result<T, ExecutionError>;
12
13#[derive(Clone)]
19pub struct FunctionContext<'context> {
20 pub name: Arc<String>,
21 pub this: Option<Value>,
22 pub ptx: &'context Context<'context>,
23 pub args: Vec<Expression>,
24 pub arg_idx: usize,
25}
26
27impl<'context> FunctionContext<'context> {
28 pub fn new(
29 name: Arc<String>,
30 this: Option<Value>,
31 ptx: &'context Context<'context>,
32 args: Vec<Expression>,
33 ) -> Self {
34 Self {
35 name,
36 this,
37 ptx,
38 args,
39 arg_idx: 0,
40 }
41 }
42
43 pub fn resolve<R>(&self, resolver: R) -> Result<Value>
45 where
46 R: Resolver,
47 {
48 resolver.resolve(self)
49 }
50
51 pub fn error<M: ToString>(&self, message: M) -> ExecutionError {
53 ExecutionError::function_error(self.name.as_str(), message)
54 }
55}
56
57pub fn size(ftx: &FunctionContext, This(this): This<Value>) -> Result<i64> {
77 let size = match this {
78 Value::List(l) => l.len(),
79 Value::Map(m) => m.map.len(),
80 Value::String(s) => s.len(),
81 Value::Bytes(b) => b.len(),
82 value => return Err(ftx.error(format!("cannot determine the size of {:?}", value))),
83 };
84 Ok(size as i64)
85}
86
87pub fn contains(This(this): This<Value>, arg: Value) -> Result<Value> {
118 Ok(match this {
119 Value::List(v) => v.contains(&arg),
120 Value::Map(v) => v
121 .map
122 .contains_key(&arg.try_into().map_err(ExecutionError::UnsupportedKeyType)?),
123 Value::String(s) => {
124 if let Value::String(arg) = arg {
125 s.contains(arg.as_str())
126 } else {
127 false
128 }
129 }
130 Value::Bytes(b) => {
131 if let Value::Bytes(arg) = arg {
132 let s = arg.as_slice();
133 b.windows(arg.len()).any(|w| w == s)
134 } else {
135 false
136 }
137 }
138 _ => false,
139 }
140 .into())
141}
142
143pub fn string(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
153 Ok(match this {
154 Value::String(v) => Value::String(v.clone()),
155 #[cfg(feature = "chrono")]
156 Value::Timestamp(t) => Value::String(t.to_rfc3339().into()),
157 #[cfg(feature = "chrono")]
158 Value::Duration(v) => Value::String(crate::duration::format_duration(&v).into()),
159 Value::Int(v) => Value::String(v.to_string().into()),
160 Value::UInt(v) => Value::String(v.to_string().into()),
161 Value::Float(v) => Value::String(v.to_string().into()),
162 Value::Bytes(v) => Value::String(Arc::new(String::from_utf8_lossy(v.as_slice()).into())),
163 v => return Err(ftx.error(format!("cannot convert {:?} to string", v))),
164 })
165}
166
167pub fn bytes(value: Arc<String>) -> Result<Value> {
168 Ok(Value::Bytes(value.as_bytes().to_vec().into()))
169}
170
171pub fn double(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
173 Ok(match this {
174 Value::String(v) => v
175 .parse::<f64>()
176 .map(Value::Float)
177 .map_err(|e| ftx.error(format!("string parse error: {e}")))?,
178 Value::Float(v) => Value::Float(v),
179 Value::Int(v) => Value::Float(v as f64),
180 Value::UInt(v) => Value::Float(v as f64),
181 v => return Err(ftx.error(format!("cannot convert {:?} to double", v))),
182 })
183}
184
185pub fn uint(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
187 Ok(match this {
188 Value::String(v) => v
189 .parse::<u64>()
190 .map(Value::UInt)
191 .map_err(|e| ftx.error(format!("string parse error: {e}")))?,
192 Value::Float(v) => {
193 if v > u64::MAX as f64 || v < u64::MIN as f64 {
194 return Err(ftx.error("unsigned integer overflow"));
195 }
196 Value::UInt(v as u64)
197 }
198 Value::Int(v) => Value::UInt(
199 v.try_into()
200 .map_err(|_| ftx.error("unsigned integer overflow"))?,
201 ),
202 Value::UInt(v) => Value::UInt(v),
203 v => return Err(ftx.error(format!("cannot convert {:?} to uint", v))),
204 })
205}
206
207pub fn int(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
209 Ok(match this {
210 Value::String(v) => v
211 .parse::<i64>()
212 .map(Value::Int)
213 .map_err(|e| ftx.error(format!("string parse error: {e}")))?,
214 Value::Float(v) => {
215 if v > i64::MAX as f64 || v < i64::MIN as f64 {
216 return Err(ftx.error("integer overflow"));
217 }
218 Value::Int(v as i64)
219 }
220 Value::Int(v) => Value::Int(v),
221 Value::UInt(v) => Value::Int(v.try_into().map_err(|_| ftx.error("integer overflow"))?),
222 v => return Err(ftx.error(format!("cannot convert {:?} to int", v))),
223 })
224}
225
226pub fn starts_with(This(this): This<Arc<String>>, prefix: Arc<String>) -> bool {
233 this.starts_with(prefix.as_str())
234}
235
236pub fn ends_with(This(this): This<Arc<String>>, suffix: Arc<String>) -> bool {
243 this.ends_with(suffix.as_str())
244}
245
246#[cfg(feature = "regex")]
253pub fn matches(
254 ftx: &FunctionContext,
255 This(this): This<Arc<String>>,
256 regex: Arc<String>,
257) -> Result<bool> {
258 match regex::Regex::new(®ex) {
259 Ok(re) => Ok(re.is_match(&this)),
260 Err(err) => Err(ftx.error(format!("'{regex}' not a valid regex:\n{err}"))),
261 }
262}
263
264pub fn has(ftx: &FunctionContext) -> Result<Value> {
278 match ftx.resolve(Argument(0)) {
281 Ok(_) => Value::Bool(true),
282 Err(err) => match err {
283 ExecutionError::NoSuchKey(_) => Value::Bool(false),
284 _ => return Err(err),
285 },
286 }
287 .into()
288}
289
290pub fn map(
301 ftx: &FunctionContext,
302 This(this): This<Value>,
303 ident: Identifier,
304 expr: Expression,
305) -> Result<Value> {
306 match this {
307 Value::List(items) => {
308 let mut values = Vec::with_capacity(items.len());
309 let mut ptx = ftx.ptx.new_inner_scope();
310 for item in items.iter() {
311 ptx.add_variable_from_value(ident.clone(), item.clone());
312 let value = ptx.resolve(&expr)?;
313 values.push(value);
314 }
315 Value::List(Arc::new(values))
316 }
317 Value::Map(map) => {
318 let mut values = Vec::with_capacity(map.map.len());
319 let mut ptx = ftx.ptx.new_inner_scope();
320 for (key, _) in map.map.iter() {
321 ptx.add_variable_from_value(ident.clone(), key.clone());
322 let value = ptx.resolve(&expr)?;
323 values.push(value);
324 }
325 Value::List(Arc::new(values))
326 }
327 _ => return Err(this.error_expected_type(ValueType::List)),
328 }
329 .into()
330}
331
332pub fn filter(
344 ftx: &FunctionContext,
345 This(this): This<Value>,
346 ident: Identifier,
347 expr: Expression,
348) -> Result<Value> {
349 match this {
350 Value::List(items) => {
351 let mut values = Vec::with_capacity(items.len());
352 let mut ptx = ftx.ptx.new_inner_scope();
353 for item in items.iter() {
354 ptx.add_variable_from_value(ident.clone(), item.clone());
355 if let Value::Bool(true) = ptx.resolve(&expr)? {
356 values.push(item.clone());
357 }
358 }
359 Value::List(Arc::new(values))
360 }
361 _ => return Err(this.error_expected_type(ValueType::List)),
362 }
363 .into()
364}
365
366pub fn all(
379 ftx: &FunctionContext,
380 This(this): This<Value>,
381 ident: Identifier,
382 expr: Expression,
383) -> Result<bool> {
384 match this {
385 Value::List(items) => {
386 let mut ptx = ftx.ptx.new_inner_scope();
387 for item in items.iter() {
388 ptx.add_variable_from_value(&ident, item);
389 if let Value::Bool(false) = ptx.resolve(&expr)? {
390 return Ok(false);
391 }
392 }
393 Ok(true)
394 }
395 Value::Map(value) => {
396 let mut ptx = ftx.ptx.new_inner_scope();
397 for key in value.map.keys() {
398 ptx.add_variable_from_value(&ident, key);
399 if let Value::Bool(false) = ptx.resolve(&expr)? {
400 return Ok(false);
401 }
402 }
403 Ok(true)
404 }
405 _ => Err(this.error_expected_type(ValueType::List)),
406 }
407}
408
409pub fn exists(
423 ftx: &FunctionContext,
424 This(this): This<Value>,
425 ident: Identifier,
426 expr: Expression,
427) -> Result<bool> {
428 match this {
429 Value::List(items) => {
430 let mut ptx = ftx.ptx.new_inner_scope();
431 for item in items.iter() {
432 ptx.add_variable_from_value(&ident, item);
433 if let Value::Bool(true) = ptx.resolve(&expr)? {
434 return Ok(true);
435 }
436 }
437 Ok(false)
438 }
439 Value::Map(value) => {
440 let mut ptx = ftx.ptx.new_inner_scope();
441 for key in value.map.keys() {
442 ptx.add_variable_from_value(&ident, key);
443 if let Value::Bool(true) = ptx.resolve(&expr)? {
444 return Ok(true);
445 }
446 }
447 Ok(false)
448 }
449 _ => Err(this.error_expected_type(ValueType::List)),
450 }
451}
452
453pub fn exists_one(
468 ftx: &FunctionContext,
469 This(this): This<Value>,
470 ident: Identifier,
471 expr: Expression,
472) -> Result<bool> {
473 match this {
474 Value::List(items) => {
475 let mut ptx = ftx.ptx.new_inner_scope();
476 let mut exists = false;
477 for item in items.iter() {
478 ptx.add_variable_from_value(&ident, item);
479 if let Value::Bool(true) = ptx.resolve(&expr)? {
480 if exists {
481 return Ok(false);
482 }
483 exists = true;
484 }
485 }
486 Ok(exists)
487 }
488 Value::Map(value) => {
489 let mut ptx = ftx.ptx.new_inner_scope();
490 let mut exists = false;
491 for key in value.map.keys() {
492 ptx.add_variable_from_value(&ident, key);
493 if let Value::Bool(true) = ptx.resolve(&expr)? {
494 if exists {
495 return Ok(false);
496 }
497 exists = true;
498 }
499 }
500 Ok(exists)
501 }
502 _ => Err(this.error_expected_type(ValueType::List)),
503 }
504}
505
506#[cfg(feature = "chrono")]
507pub use time::duration;
508#[cfg(feature = "chrono")]
509pub use time::timestamp;
510
511#[cfg(feature = "chrono")]
512pub mod time {
513 use super::Result;
514 use crate::magic::This;
515 use crate::{ExecutionError, Value};
516 use chrono::{Datelike, Days, Months, Timelike};
517 use std::sync::Arc;
518
519 pub fn duration(value: Arc<String>) -> crate::functions::Result<Value> {
535 Ok(Value::Duration(_duration(value.as_str())?))
536 }
537
538 pub fn timestamp(value: Arc<String>) -> Result<Value> {
541 Ok(Value::Timestamp(
542 chrono::DateTime::parse_from_rfc3339(value.as_str())
543 .map_err(|e| ExecutionError::function_error("timestamp", e.to_string().as_str()))?,
544 ))
545 }
546
547 fn _duration(i: &str) -> Result<chrono::Duration> {
550 let (_, duration) = crate::duration::parse_duration(i)
551 .map_err(|e| ExecutionError::function_error("duration", e.to_string()))?;
552 Ok(duration)
553 }
554
555 fn _timestamp(i: &str) -> Result<chrono::DateTime<chrono::FixedOffset>> {
556 chrono::DateTime::parse_from_rfc3339(i)
557 .map_err(|e| ExecutionError::function_error("timestamp", e.to_string()))
558 }
559
560 pub fn timestamp_year(
561 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
562 ) -> Result<Value> {
563 Ok(this.year().into())
564 }
565
566 pub fn timestamp_month(
567 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
568 ) -> Result<Value> {
569 Ok((this.month0() as i32).into())
570 }
571
572 pub fn timestamp_year_day(
573 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
574 ) -> Result<Value> {
575 let year = this
576 .checked_sub_days(Days::new(this.day0() as u64))
577 .unwrap()
578 .checked_sub_months(Months::new(this.month0()))
579 .unwrap();
580 Ok(this.signed_duration_since(year).num_days().into())
581 }
582
583 pub fn timestamp_month_day(
584 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
585 ) -> Result<Value> {
586 Ok((this.day0() as i32).into())
587 }
588
589 pub fn timestamp_date(
590 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
591 ) -> Result<Value> {
592 Ok((this.day() as i32).into())
593 }
594
595 pub fn timestamp_weekday(
596 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
597 ) -> Result<Value> {
598 Ok((this.weekday().num_days_from_sunday() as i32).into())
599 }
600
601 pub fn timestamp_hours(
602 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
603 ) -> Result<Value> {
604 Ok((this.hour() as i32).into())
605 }
606
607 pub fn timestamp_minutes(
608 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
609 ) -> Result<Value> {
610 Ok((this.minute() as i32).into())
611 }
612
613 pub fn timestamp_seconds(
614 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
615 ) -> Result<Value> {
616 Ok((this.second() as i32).into())
617 }
618
619 pub fn timestamp_millis(
620 This(this): This<chrono::DateTime<chrono::FixedOffset>>,
621 ) -> Result<Value> {
622 Ok((this.timestamp_subsec_millis() as i32).into())
623 }
624}
625
626pub fn max(Arguments(args): Arguments) -> Result<Value> {
627 let items = if args.len() == 1 {
629 match &args[0] {
630 Value::List(values) => values,
631 _ => return Ok(args[0].clone()),
632 }
633 } else {
634 &args
635 };
636
637 items
638 .iter()
639 .skip(1)
640 .try_fold(items.first().unwrap_or(&Value::Null), |acc, x| {
641 match acc.partial_cmp(x) {
642 Some(Ordering::Greater) => Ok(acc),
643 Some(_) => Ok(x),
644 None => Err(ExecutionError::ValuesNotComparable(acc.clone(), x.clone())),
645 }
646 })
647 .cloned()
648}
649
650pub fn min(Arguments(args): Arguments) -> Result<Value> {
651 let items = if args.len() == 1 {
653 match &args[0] {
654 Value::List(values) => values,
655 _ => return Ok(args[0].clone()),
656 }
657 } else {
658 &args
659 };
660
661 items
662 .iter()
663 .skip(1)
664 .try_fold(items.first().unwrap_or(&Value::Null), |acc, x| {
665 match acc.partial_cmp(x) {
666 Some(Ordering::Less) => Ok(acc),
667 Some(_) => Ok(x),
668 None => Err(ExecutionError::ValuesNotComparable(acc.clone(), x.clone())),
669 }
670 })
671 .cloned()
672}
673
674#[cfg(test)]
675mod tests {
676 use crate::context::Context;
677 use crate::tests::test_script;
678
679 fn assert_script(input: &(&str, &str)) {
680 assert_eq!(test_script(input.1, None), Ok(true.into()), "{}", input.0);
681 }
682
683 #[test]
684 fn test_size() {
685 [
686 ("size of list", "size([1, 2, 3]) == 3"),
687 ("size of map", "size({'a': 1, 'b': 2, 'c': 3}) == 3"),
688 ("size of string", "size('foo') == 3"),
689 ("size of bytes", "size(b'foo') == 3"),
690 ("size as a list method", "[1, 2, 3].size() == 3"),
691 ("size as a string method", "'foobar'.size() == 6"),
692 ]
693 .iter()
694 .for_each(assert_script);
695 }
696
697 #[test]
698 fn test_has() {
699 let tests = vec![
700 ("map has", "has(foo.bar) == true"),
701 ("map has", "has(foo.bar) == true"),
702 ("map not has", "has(foo.baz) == false"),
703 ("map deep not has", "has(foo.baz.bar) == false"),
704 ];
705
706 for (name, script) in tests {
707 let mut ctx = Context::default();
708 ctx.add_variable_from_value("foo", std::collections::HashMap::from([("bar", 1)]));
709 assert_eq!(test_script(script, Some(ctx)), Ok(true.into()), "{}", name);
710 }
711 }
712
713 #[test]
714 fn test_map() {
715 [
716 ("map list", "[1, 2, 3].map(x, x * 2) == [2, 4, 6]"),
717 ("map list 2", "[1, 2, 3].map(y, y + 1) == [2, 3, 4]"),
718 (
719 "nested map",
720 "[[1, 2], [2, 3]].map(x, x.map(x, x * 2)) == [[2, 4], [4, 6]]",
721 ),
722 (
723 "map to list",
724 r#"{'John': 'smart'}.map(key, key) == ['John']"#,
725 ),
726 ]
727 .iter()
728 .for_each(assert_script);
729 }
730
731 #[test]
732 fn test_filter() {
733 [("filter list", "[1, 2, 3].filter(x, x > 2) == [3]")]
734 .iter()
735 .for_each(assert_script);
736 }
737
738 #[test]
739 fn test_all() {
740 [
741 ("all list #1", "[0, 1, 2].all(x, x >= 0)"),
742 ("all list #2", "[0, 1, 2].all(x, x > 0) == false"),
743 ("all map", "{0: 0, 1:1, 2:2}.all(x, x >= 0) == true"),
744 ]
745 .iter()
746 .for_each(assert_script);
747 }
748
749 #[test]
750 fn test_exists() {
751 [
752 ("exist list #1", "[0, 1, 2].exists(x, x > 0)"),
753 ("exist list #2", "[0, 1, 2].exists(x, x == 3) == false"),
754 ("exist list #3", "[0, 1, 2, 2].exists(x, x == 2)"),
755 ("exist map", "{0: 0, 1:1, 2:2}.exists(x, x > 0)"),
756 ]
757 .iter()
758 .for_each(assert_script);
759 }
760
761 #[test]
762 fn test_exists_one() {
763 [
764 ("exist list #1", "[0, 1, 2].exists_one(x, x > 0) == false"),
765 ("exist list #2", "[0, 1, 2].exists_one(x, x == 0)"),
766 ("exist map", "{0: 0, 1:1, 2:2}.exists_one(x, x == 2)"),
767 ]
768 .iter()
769 .for_each(assert_script);
770 }
771
772 #[test]
773 fn test_max() {
774 [
775 ("max single", "max(1) == 1"),
776 ("max multiple", "max(1, 2, 3) == 3"),
777 ("max negative", "max(-1, 0) == 0"),
778 ("max float", "max(-1.0, 0.0) == 0.0"),
779 ("max list", "max([1, 2, 3]) == 3"),
780 ("max empty list", "max([]) == null"),
781 ("max no args", "max() == null"),
782 ]
783 .iter()
784 .for_each(assert_script);
785 }
786
787 #[test]
788 fn test_min() {
789 [
790 ("min single", "min(1) == 1"),
791 ("min multiple", "min(1, 2, 3) == 1"),
792 ("min negative", "min(-1, 0) == -1"),
793 ("min float", "min(-1.0, 0.0) == -1.0"),
794 (
795 "min float multiple",
796 "min(1.61803, 3.1415, 2.71828, 1.41421) == 1.41421",
797 ),
798 ("min list", "min([1, 2, 3]) == 1"),
799 ("min empty list", "min([]) == null"),
800 ("min no args", "min() == null"),
801 ]
802 .iter()
803 .for_each(assert_script);
804 }
805
806 #[test]
807 fn test_starts_with() {
808 [
809 ("starts with true", "'foobar'.startsWith('foo') == true"),
810 ("starts with false", "'foobar'.startsWith('bar') == false"),
811 ]
812 .iter()
813 .for_each(assert_script);
814 }
815
816 #[test]
817 fn test_ends_with() {
818 [
819 ("ends with true", "'foobar'.endsWith('bar') == true"),
820 ("ends with false", "'foobar'.endsWith('foo') == false"),
821 ]
822 .iter()
823 .for_each(assert_script);
824 }
825
826 #[cfg(feature = "chrono")]
827 #[test]
828 fn test_timestamp() {
829 [(
830 "comparison",
831 "timestamp('2023-05-29T00:00:00Z') > timestamp('2023-05-28T00:00:00Z')",
832 ),
833 (
834 "comparison",
835 "timestamp('2023-05-29T00:00:00Z') < timestamp('2023-05-30T00:00:00Z')",
836 ),
837 (
838 "subtracting duration",
839 "timestamp('2023-05-29T00:00:00Z') - duration('24h') == timestamp('2023-05-28T00:00:00Z')",
840 ),
841 (
842 "subtracting date",
843 "timestamp('2023-05-29T00:00:00Z') - timestamp('2023-05-28T00:00:00Z') == duration('24h')",
844 ),
845 (
846 "adding duration",
847 "timestamp('2023-05-28T00:00:00Z') + duration('24h') == timestamp('2023-05-29T00:00:00Z')",
848 ),
849 (
850 "timestamp string",
851 "timestamp('2023-05-28T00:00:00Z').string() == '2023-05-28T00:00:00+00:00'",
852 ),
853 (
854 "timestamp getFullYear",
855 "timestamp('2023-05-28T00:00:00Z').getFullYear() == 2023",
856 ),
857 (
858 "timestamp getMonth",
859 "timestamp('2023-05-28T00:00:00Z').getMonth() == 4",
860 ),
861 (
862 "timestamp getDayOfMonth",
863 "timestamp('2023-05-28T00:00:00Z').getDayOfMonth() == 27",
864 ),
865 (
866 "timestamp getDayOfYear",
867 "timestamp('2023-05-28T00:00:00Z').getDayOfYear() == 147",
868 ),
869 (
870 "timestamp getDate",
871 "timestamp('2023-05-28T00:00:00Z').getDate() == 28",
872 ),
873 (
874 "timestamp getDayOfWeek",
875 "timestamp('2023-05-28T00:00:00Z').getDayOfWeek() == 0",
876 ),
877 (
878 "timestamp getHours",
879 "timestamp('2023-05-28T02:00:00Z').getHours() == 2",
880 ),
881 (
882 "timestamp getMinutes",
883 " timestamp('2023-05-28T00:05:00Z').getMinutes() == 5",
884 ),
885 (
886 "timestamp getSeconds",
887 "timestamp('2023-05-28T00:00:06Z').getSeconds() == 6",
888 ),
889 (
890 "timestamp getMilliseconds",
891 "timestamp('2023-05-28T00:00:42.123Z').getMilliseconds() == 123",
892 ),
893
894 ]
895 .iter()
896 .for_each(assert_script);
897 }
898
899 #[cfg(feature = "chrono")]
900 #[test]
901 fn test_duration() {
902 [
903 ("duration equal 1", "duration('1s') == duration('1000ms')"),
904 ("duration equal 2", "duration('1m') == duration('60s')"),
905 ("duration equal 3", "duration('1h') == duration('60m')"),
906 ("duration comparison 1", "duration('1m') > duration('1s')"),
907 ("duration comparison 2", "duration('1m') < duration('1h')"),
908 (
909 "duration subtraction",
910 "duration('1h') - duration('1m') == duration('59m')",
911 ),
912 (
913 "duration addition",
914 "duration('1h') + duration('1m') == duration('1h1m')",
915 ),
916 ]
917 .iter()
918 .for_each(assert_script);
919 }
920
921 #[cfg(feature = "chrono")]
922 #[test]
923 fn test_timestamp_variable() {
924 let mut context = Context::default();
925 let ts: chrono::DateTime<chrono::FixedOffset> =
926 chrono::DateTime::parse_from_rfc3339("2023-05-29T00:00:00Z").unwrap();
927 context
928 .add_variable("ts", crate::Value::Timestamp(ts))
929 .unwrap();
930
931 let program = crate::Program::compile("ts == timestamp('2023-05-29T00:00:00Z')").unwrap();
932 let result = program.execute(&context).unwrap();
933 assert_eq!(result, true.into());
934 }
935
936 #[cfg(feature = "chrono")]
937 #[test]
938 fn test_chrono_string() {
939 [
940 ("duration", "duration('1h30m').string() == '1h30m0s'"),
941 (
942 "timestamp",
943 "timestamp('2023-05-29T00:00:00Z').string() == '2023-05-29T00:00:00+00:00'",
944 ),
945 ]
946 .iter()
947 .for_each(assert_script);
948 }
949
950 #[test]
951 fn test_contains() {
952 let tests = vec![
953 ("list", "[1, 2, 3].contains(3) == true"),
954 ("map", "{1: true, 2: true, 3: true}.contains(3) == true"),
955 ("string", "'foobar'.contains('bar') == true"),
956 ("bytes", "b'foobar'.contains(b'o') == true"),
957 ];
958
959 for (name, script) in tests {
960 assert_eq!(test_script(script, None), Ok(true.into()), "{}", name);
961 }
962 }
963
964 #[cfg(feature = "regex")]
965 #[test]
966 fn test_matches() {
967 let tests = vec![
968 ("string", "'foobar'.matches('^[a-zA-Z]*$') == true"),
969 (
970 "map",
971 "{'1': 'abc', '2': 'def', '3': 'ghi'}.all(key, key.matches('^[a-zA-Z]*$')) == false",
972 ),
973 ];
974
975 for (name, script) in tests {
976 assert_eq!(
977 test_script(script, None),
978 Ok(true.into()),
979 ".matches failed for '{name}'"
980 );
981 }
982 }
983
984 #[cfg(feature = "regex")]
985 #[test]
986 fn test_matches_err() {
987 assert_eq!(
988 test_script(
989 "'foobar'.matches('(foo') == true", None),
990 Err(
991 crate::ExecutionError::FunctionError {
992 function: "matches".to_string(),
993 message: "'(foo' not a valid regex:\nregex parse error:\n (foo\n ^\nerror: unclosed group".to_string()
994 }
995 )
996 );
997 }
998
999 #[test]
1000 fn test_string() {
1001 [
1002 ("string", "'foo'.string() == 'foo'"),
1003 ("int", "10.string() == '10'"),
1004 ("float", "10.5.string() == '10.5'"),
1005 ("bytes", "b'foo'.string() == 'foo'"),
1006 ]
1007 .iter()
1008 .for_each(assert_script);
1009 }
1010
1011 #[test]
1012 fn test_bytes() {
1013 [
1014 ("string", "bytes('abc') == b'abc'"),
1015 ("bytes", "bytes('abc') == b'\\x61b\\x63'"),
1016 ]
1017 .iter()
1018 .for_each(assert_script);
1019 }
1020
1021 #[test]
1022 fn test_double() {
1023 [
1024 ("string", "'10'.double() == 10.0"),
1025 ("int", "10.double() == 10.0"),
1026 ("double", "10.0.double() == 10.0"),
1027 ]
1028 .iter()
1029 .for_each(assert_script);
1030 }
1031
1032 #[test]
1033 fn test_uint() {
1034 [
1035 ("string", "'10'.uint() == 10.uint()"),
1036 ("double", "10.5.uint() == 10.uint()"),
1037 ]
1038 .iter()
1039 .for_each(assert_script);
1040 }
1041
1042 #[test]
1043 fn test_int() {
1044 [
1045 ("string", "'10'.int() == 10"),
1046 ("int", "10.int() == 10"),
1047 ("uint", "10.uint().int() == 10"),
1048 ("double", "10.5.int() == 10"),
1049 ]
1050 .iter()
1051 .for_each(assert_script);
1052 }
1053}