1use std::{cmp::Ordering, convert::TryInto, sync::Arc};
2
3use crate::{
4 ExecutionError,
5 context::Context,
6 magic::{Arguments, This},
7 objects::Value,
8 parser::Expression,
9 resolvers::Resolver,
10};
11
12type Result<T> = std::result::Result<T, ExecutionError>;
13
14#[derive(Clone)]
20pub struct FunctionContext<'context> {
21 pub name: Arc<String>,
22 pub this: Option<Value>,
23 pub ptx: &'context Context<'context>,
24 pub args: Vec<Expression>,
25 pub arg_idx: usize,
26}
27
28impl<'context> FunctionContext<'context> {
29 pub fn new(
30 name: Arc<String>,
31 this: Option<Value>,
32 ptx: &'context Context<'context>,
33 args: Vec<Expression>,
34 ) -> Self {
35 Self {
36 name,
37 this,
38 ptx,
39 args,
40 arg_idx: 0,
41 }
42 }
43
44 pub fn resolve<R>(&self, resolver: R) -> Result<Value>
46 where
47 R: Resolver,
48 {
49 resolver.resolve(self)
50 }
51
52 pub fn error<M: ToString>(&self, message: M) -> ExecutionError {
54 ExecutionError::function_error(self.name.as_str(), message)
55 }
56}
57
58pub fn length(ftx: &FunctionContext, This(this): This<Value>) -> Result<i64> {
78 let length = match this {
79 Value::List(l) => l.len(),
80 Value::Map(m) => m.map.len(),
81 Value::String(s) => s.len(),
82 Value::Bytes(b) => b.len(),
83 value => return Err(ftx.error(format!("cannot determine the length of {value:?}"))),
84 };
85 Ok(length as i64)
86}
87
88pub fn contains(This(this): This<Value>, arg: Value) -> Result<Value> {
119 Ok(match this {
120 Value::List(v) => v.contains(&arg),
121 Value::Map(v) => v
122 .map
123 .contains_key(&arg.try_into().map_err(ExecutionError::UnsupportedKeyType)?),
124 Value::String(s) => {
125 if let Value::String(arg) = arg {
126 s.contains(arg.as_str())
127 } else {
128 false
129 }
130 }
131 Value::Bytes(b) => {
132 if let Value::Bytes(arg) = arg {
133 let s = arg.as_slice();
134 b.windows(arg.len()).any(|w| w == s)
135 } else {
136 false
137 }
138 }
139 #[cfg(feature = "ip")]
140 Value::Ip(v) => {
141 if let Value::Ip(arg) = arg {
142 let is_arg_single_ip = match arg {
143 ipnetwork::IpNetwork::V4(v4) => v4.prefix() == 32,
144 ipnetwork::IpNetwork::V6(v6) => v6.prefix() == 128,
145 };
146 is_arg_single_ip && v.contains(arg.ip())
147 } else {
148 false
149 }
150 }
151 _ => false,
152 }
153 .into())
154}
155
156pub fn string(ftx: &FunctionContext, value: Value) -> Result<Value> {
166 Ok(match value {
167 Value::String(v) => Value::String(v.clone()),
168 #[cfg(feature = "time")]
169 Value::Timestamp(t) => Value::String(t.to_string().into()),
170 #[cfg(feature = "time")]
171 Value::Duration(v) => Value::String(crate::duration::format_duration(&v).into()),
172 Value::Int(v) => Value::String(v.to_string().into()),
173 Value::Float(v) => Value::String(v.to_string().into()),
175 Value::Bytes(v) => Value::String(Arc::new(String::from_utf8_lossy(v.as_slice()).into())),
176 #[cfg(feature = "regex")]
177 Value::Regex(regex) => Value::String(Arc::new(regex.to_string())),
178 #[cfg(feature = "ip")]
179 Value::Ip(ip) => Value::String(Arc::new(ip.to_string())),
180 v => return Err(ftx.error(format!("cannot convert {v:?} to string"))),
181 })
182}
183
184pub fn bytes(value: Arc<String>) -> Result<Value> {
185 Ok(Value::Bytes(value.as_bytes().to_vec().into()))
186}
187
188pub fn float(ftx: &FunctionContext, value: Value) -> Result<Value> {
190 Ok(match value {
191 Value::String(v) => v
192 .parse::<f64>()
193 .map(Value::Float)
194 .map_err(|e| ftx.error(format!("string parse error: {e}")))?,
195 Value::Float(v) => Value::Float(v),
196 Value::Int(v) => Value::Float(v as f64),
197 v => return Err(ftx.error(format!("cannot convert {v:?} to Float"))),
199 })
200}
201
202pub fn int(ftx: &FunctionContext, value: Value) -> Result<Value> {
226 Ok(match value {
227 Value::String(v) => v
228 .parse::<i64>()
229 .map(Value::Int)
230 .map_err(|e| ftx.error(format!("string parse error: {e}")))?,
231 Value::Float(v) => {
232 if v > i64::MAX as f64 || v < i64::MIN as f64 {
233 return Err(ftx.error("integer overflow"));
234 }
235 Value::Int(v as i64)
236 }
237 Value::Int(v) => Value::Int(v),
238 v => return Err(ftx.error(format!("cannot convert {v:?} to int"))),
240 })
241}
242
243pub fn starts_with(This(this): This<Arc<String>>, prefix: Arc<String>) -> bool {
250 this.starts_with(prefix.as_str())
251}
252
253pub fn ends_with(This(this): This<Arc<String>>, suffix: Arc<String>) -> bool {
260 this.ends_with(suffix.as_str())
261}
262
263#[cfg(feature = "regex")]
270pub fn matches(
271 This(this): This<Arc<String>>,
273 regex: regex::Regex,
274) -> bool {
275 regex.is_match(&this)
276 }
285
286#[cfg(feature = "regex")]
287pub fn regex(ftx: &FunctionContext, This(this): This<Value>) -> Result<Value> {
288 Ok(match this {
289 Value::String(v) => Value::Regex(regex::Regex::new(v.as_str()).map_err(|e| ftx.error(e.to_string()))?),
290 v => return Err(ftx.error(format!("cannot convert {v:?} to Regex"))),
291 })
292}
293
294#[cfg(feature = "time")]
295pub use time::duration;
296#[cfg(feature = "time")]
297pub use time::timestamp;
298
299#[cfg(feature = "ip")]
301pub fn ip(ftx: &FunctionContext, value: Value) -> Result<Value> {
302 match value {
303 Value::String(v) => {
304 use ipnetwork::IpNetwork;
305 let ip: IpNetwork = v
306 .parse()
307 .map_err(|err| ftx.error(format!("error converting {v:?} to Ip: {err}")))?;
308 Ok(Value::Ip(ip))
309 }
310 v => Err(ftx.error(format!("cannot convert {v:?} to String"))),
311 }
312}
313
314#[cfg(feature = "time")]
315pub mod time {
316 use std::sync::Arc;
317
318 use time::{DateTime, TimeZone};
319
320 use super::Result;
321 use crate::{ExecutionError, Value, magic::This};
322
323 pub fn duration(value: Arc<String>) -> crate::functions::Result<Value> {
339 Ok(Value::Duration(_duration(value.as_str())?))
340 }
341
342 pub fn timestamp(value: Arc<String>) -> Result<Value> {
344 Ok(Value::Timestamp(_timestamp(value.as_str())?))
345 }
346
347 fn _duration(i: &str) -> Result<crate::duration::Duration> {
350 let (_, duration) = crate::duration::parse_duration(i)
351 .map_err(|e| ExecutionError::function_error("duration", e.to_string()))?;
352 Ok(duration)
353 }
354
355 fn _timestamp(i: &str) -> Result<DateTime> {
356 i.parse::<DateTime>()
357 .map_err(|e| ExecutionError::function_error("timestamp", e.to_string()))
358 }
359
360 pub fn timestamp_year(This(this): This<DateTime>) -> Result<Value> {
361 Ok(this.year().into())
362 }
363
364 pub fn timestamp_month(This(this): This<DateTime>) -> Result<Value> {
365 Ok(((this.month() as i32) - 1).into())
366 }
367
368 pub fn timestamp_year_day(This(this): This<DateTime>) -> Result<Value> {
369 Ok(((this.ordinal() as i32) - 1).into())
370 }
371
372 pub fn timestamp_month_day(This(this): This<DateTime>) -> Result<Value> {
373 Ok(((this.day() as i32) - 1).into())
374 }
375
376 pub fn timestamp_date(This(this): This<DateTime>) -> Result<Value> {
377 Ok((this.day() as i32).into())
378 }
379
380 pub fn timestamp_weekday(This(this): This<DateTime>) -> Result<Value> {
381 Ok((this.weekday().number_from_sunday() as i32).into())
382 }
383
384 pub fn timestamp_hours(This(this): This<DateTime>) -> Result<Value> {
385 Ok((this.hour() as i32).into())
386 }
387
388 pub fn timestamp_minutes(This(this): This<DateTime>) -> Result<Value> {
389 Ok((this.minute() as i32).into())
390 }
391
392 pub fn timestamp_seconds(This(this): This<DateTime>) -> Result<Value> {
393 Ok((this.second() as i32).into())
394 }
395
396 pub fn timestamp_millis(This(this): This<DateTime>) -> Result<Value> {
397 Ok((this.millisecond() as i32).into())
398 }
399
400 pub fn now() -> Result<Value> {
401 Ok(Value::Timestamp(DateTime::now_in(TimeZone::UTC)))
402 }
403
404 pub fn unix(This(this): This<DateTime>) -> Result<Value> {
405 Ok((this.unix()).into())
406 }
407}
408
409pub fn max(Arguments(args): Arguments) -> Result<Value> {
410 let items = if args.len() == 1 {
412 match &args[0] {
413 Value::List(values) => values,
414 _ => return Ok(args[0].clone()),
415 }
416 } else {
417 &args
418 };
419
420 items
421 .iter()
422 .skip(1)
423 .try_fold(items.first().unwrap_or(&Value::Null), |acc, x| match acc.partial_cmp(x) {
424 Some(Ordering::Greater) => Ok(acc),
425 Some(_) => Ok(x),
426 None => Err(ExecutionError::ValuesNotComparable(acc.clone(), x.clone())),
427 })
428 .cloned()
429}
430
431pub fn min(Arguments(args): Arguments) -> Result<Value> {
432 let items = if args.len() == 1 {
434 match &args[0] {
435 Value::List(values) => values,
436 _ => return Ok(args[0].clone()),
437 }
438 } else {
439 &args
440 };
441
442 items
443 .iter()
444 .skip(1)
445 .try_fold(items.first().unwrap_or(&Value::Null), |acc, x| match acc.partial_cmp(x) {
446 Some(Ordering::Less) => Ok(acc),
447 Some(_) => Ok(x),
448 None => Err(ExecutionError::ValuesNotComparable(acc.clone(), x.clone())),
449 })
450 .cloned()
451}
452
453#[cfg(test)]
454mod tests {
455 use crate::{context::Context, tests::test_script};
456
457 fn assert_script(input: &(&str, &str)) {
458 assert_eq!(test_script(input.1, None), Ok(true.into()), "{}", input.0);
459 }
460
461 fn assert_error(input: &(&str, &str, &str)) {
462 assert_eq!(
463 test_script(input.1, None).expect_err("expected error").to_string(),
464 input.2,
465 "{}",
466 input.0
467 );
468 }
469
470 #[test]
471 fn test_length() {
472 [
473 ("length of list", "length([1, 2, 3]) == 3"),
474 ("length of map", r#"length({"a": 1, "b": 2, "c": 3}) == 3"#),
475 ("length of string", r#"length("foo") == 3"#),
476 ("length of bytes", r#"length(b"foo") == 3"#),
477 ("length as a list method", "[1, 2, 3].length() == 3"),
478 ("length as a string method", r#""foobar".length() == 6"#),
479 ]
480 .iter()
481 .for_each(assert_script);
482 }
483
484 #[test]
485 fn test_has() {
486 let tests = vec![
487 ("map has", "has(foo.bar) == true"),
488 ("map not has", "has(foo.baz) == false"),
489 ];
490
491 for (name, script) in tests {
492 let mut ctx = Context::default();
493 ctx.add_variable_from_value("foo", std::collections::HashMap::from([("bar", 1)]));
494 assert_eq!(test_script(script, Some(ctx)), Ok(true.into()), "{name}");
495 }
496 }
497
498 #[test]
499 fn test_map() {
500 [
501 ("map list", "[1, 2, 3].map(x, x * 2) == [2, 4, 6]"),
502 ("map list 2", "[1, 2, 3].map(y, y + 1) == [2, 3, 4]"),
503 ("map list filter", "[1, 2, 3].map(y, y + 1) == [2, 3, 4]"),
504 ("nested map", "[[1, 2], [2, 3]].map(x, x.map(x, x * 2)) == [[2, 4], [4, 6]]"),
505 ("map to list", r#"{"John": "smart"}.map(key, key) == ["John"]"#),
506 ]
507 .iter()
508 .for_each(assert_script);
509 }
510
511 #[test]
512 fn test_filter() {
513 [("filter list", "[1, 2, 3].filter(x, x > 2) == [3]")]
514 .iter()
515 .for_each(assert_script);
516 }
517
518 #[test]
519 fn test_all() {
520 [
521 ("all list #1", "[0, 1, 2].all(x, x >= 0)"),
522 ("all list #2", "[0, 1, 2].all(x, x > 0) == false"),
523 ("all map", "{0: 0, 1:1, 2:2}.all(x, x >= 0) == true"),
524 ]
525 .iter()
526 .for_each(assert_script);
527 }
528
529 #[test]
530 fn test_any() {
531 [
532 ("exist list #1", "[0, 1, 2].any(x, x > 0)"),
533 ("exist list #2", "[0, 1, 2].any(x, x == 3) == false"),
534 ("exist list #3", "[0, 1, 2, 2].any(x, x == 2)"),
535 ("exist map", "{0: 0, 1:1, 2:2}.any(x, x > 0)"),
536 ]
537 .iter()
538 .for_each(assert_script);
539 }
540
541 #[test]
553 fn test_max() {
554 [
555 ("max single", "max(1) == 1"),
556 ("max multiple", "max(1, 2, 3) == 3"),
557 ("max negative", "max(-1, 0) == 0"),
558 ("max float", "max(-1.0, 0.0) == 0.0"),
559 ("max list", "max([1, 2, 3]) == 3"),
560 ("max empty list", "max([]) == null"),
561 ("max no args", "max() == null"),
562 ]
563 .iter()
564 .for_each(assert_script);
565 }
566
567 #[test]
568 fn test_min() {
569 [
570 ("min single", "min(1) == 1"),
571 ("min multiple", "min(1, 2, 3) == 1"),
572 ("min negative", "min(-1, 0) == -1"),
573 ("min float", "min(-1.0, 0.0) == -1.0"),
574 ("min float multiple", "min(1.61803, 3.1415, 2.71828, 1.41421) == 1.41421"),
575 ("min list", "min([1, 2, 3]) == 1"),
576 ("min empty list", "min([]) == null"),
577 ("min no args", "min() == null"),
578 ]
579 .iter()
580 .for_each(assert_script);
581 }
582
583 #[test]
584 fn test_starts_with() {
585 [
586 ("starts with true", r#""foobar".starts_with("foo") == true"#),
587 ("starts with false", r#""foobar".starts_with("bar") == false"#),
588 ]
589 .iter()
590 .for_each(assert_script);
591 }
592
593 #[test]
594 fn test_ends_with() {
595 [
596 ("ends with true", r#""foobar".ends_with("bar") == true"#),
597 ("ends with false", r#""foobar".ends_with("foo") == false"#),
598 ]
599 .iter()
600 .for_each(assert_script);
601 }
602
603 #[cfg(feature = "time")]
604 #[test]
605 fn test_timestamp() {
606 [
607 (
608 "comparison",
609 r#"Timestamp("2023-05-29T00:00:00Z") > Timestamp("2023-05-28T00:00:00Z")"#,
610 ),
611 (
612 "comparison",
613 r#"Timestamp("2023-05-29T00:00:00Z") < Timestamp("2023-05-30T00:00:00Z")"#,
614 ),
615 (
616 "subtracting duration",
617 r#"Timestamp("2023-05-29T00:00:00Z") - Duration("24h") == Timestamp("2023-05-28T00:00:00Z")"#,
618 ),
619 (
620 "subtracting date",
621 r#"Timestamp("2023-05-29T00:00:00Z") - Timestamp("2023-05-28T00:00:00Z") == Duration("24h")"#,
622 ),
623 (
624 "adding duration",
625 r#"Timestamp("2023-05-28T00:00:00Z") + Duration("24h") == Timestamp("2023-05-29T00:00:00Z")"#,
626 ),
627 (
628 "timestamp string",
629 r#"String(Timestamp("2023-05-28T00:00:00Z")) == "2023-05-28T00:00:00Z""#,
630 ),
631 ("timestamp year", r#"Timestamp("2023-05-28T00:00:00Z").year() == 2023"#),
632 ("timestamp month", r#"Timestamp("2023-05-28T00:00:00Z").month() == 4"#),
633 (
634 "timestamp getDayOfMonth",
635 r#"Timestamp("2023-05-28T00:00:00Z").getDayOfMonth() == 27"#,
636 ),
637 (
638 "timestamp getDayOfYear",
639 r#"Timestamp("2023-05-28T00:00:00Z").getDayOfYear() == 147"#,
640 ),
641 ("timestamp getDate", r#"Timestamp("2023-05-28T00:00:00Z").getDate() == 28"#),
642 (
643 "timestamp getDayOfWeek",
644 r#"Timestamp("2023-05-28T00:00:00Z").getDayOfWeek() == 0"#,
645 ),
646 ("timestamp getHours", r#"Timestamp("2023-05-28T02:00:00Z").getHours() == 2"#),
647 (
648 "timestamp getMinutes",
649 r#" Timestamp("2023-05-28T00:05:00Z").getMinutes() == 5"#,
650 ),
651 ("timestamp seconds", r#"Timestamp("2023-05-28T00:00:06Z").seconds() == 6"#),
652 (
653 "timestamp milliseconds",
654 r#"Timestamp("2023-05-28T00:00:42.123Z").milliseconds() == 123"#,
655 ),
656 ]
657 .iter()
658 .for_each(assert_script);
659
660 [
661 (
662 "timestamp out of range",
663 r#"Timestamp("0000-01-00T00:00:00Z")"#,
664 "Error executing function 'timestamp': day is not valid for the given month",
665 ),
666 (
667 "timestamp out of range",
668 r#"Timestamp("9999-12-32T23:59:59.999999999Z")"#,
669 "Error executing function 'timestamp': day is not valid for the given month",
670 ),
671 (
672 "timestamp overflow",
673 r#"Timestamp("9999-12-31T23:59:59Z") + Duration("1s")"#,
674 "Overflow from binary operator 'add': Timestamp(9999-12-31T23:59:59Z), Duration(Duration { nanos: 1000000000 })",
675 ),
676 (
677 "timestamp underflow",
678 r#"Timestamp("0001-01-01T00:00:00Z") - Duration("1s")"#,
679 "Overflow from binary operator 'sub': Timestamp(0001-01-01T00:00:00Z), Duration(Duration { nanos: 1000000000 })",
680 ),
681 (
682 "timestamp underflow",
683 r#"Timestamp("0001-01-01T00:00:00Z") + Duration("-1s")"#,
684 "Overflow from binary operator 'add': Timestamp(0001-01-01T00:00:00Z), Duration(Duration { nanos: -1000000000 })",
685 ),
686 ]
687 .iter()
688 .for_each(assert_error)
689 }
690
691 #[cfg(feature = "time")]
692 #[test]
693 fn test_duration() {
694 [
695 ("duration equal 1", r#"Duration("1s") == Duration("1000ms")"#),
696 ("duration equal 2", r#"Duration("1m") == Duration("60s")"#),
697 ("duration equal 3", r#"Duration("1h") == Duration("60m")"#),
698 ("duration comparison 1", r#"Duration("1m") > Duration("1s")"#),
699 ("duration comparison 2", r#"Duration("1m") < Duration("1h")"#),
700 ("duration subtraction", r#"Duration("1h") - Duration("1m") == Duration("59m")"#),
701 ("duration addition", r#"Duration("1h") + Duration("1m") == Duration("1h1m")"#),
702 ]
703 .iter()
704 .for_each(assert_script);
705 }
706
707 #[cfg(feature = "time")]
708 #[test]
709 fn test_timestamp_variable() {
710 let mut context = Context::default();
711 let ts: time::DateTime = "2023-05-29T00:00:00Z".parse().unwrap();
712 context.add_variable("ts", crate::Value::Timestamp(ts)).unwrap();
713
714 let program = crate::Program::compile(r#"ts == Timestamp("2023-05-29T00:00:00Z")"#).unwrap();
715 let result = program.execute(&context).unwrap();
716 assert_eq!(result, true.into());
717 }
718
719 #[cfg(feature = "time")]
720 #[test]
721 fn test_time_string() {
722 [
723 ("duration", r#"String(Duration("1h30m")) == "1h30m0s""#),
724 (
725 "timestamp",
726 r#"String(Timestamp("2023-05-29T00:00:00Z")) == "2023-05-29T00:00:00Z""#,
727 ),
728 ]
729 .iter()
730 .for_each(assert_script);
731 }
732
733 #[test]
734 fn test_contains() {
735 let tests = vec![
736 ("list", "[1, 2, 3].contains(3) == true"),
737 ("map", "{1: true, 2: true, 3: true}.contains(3) == true"),
738 ("string", r#""foobar".contains("bar") == true"#),
739 ("bytes", r#"b"foobar".contains(b"o") == true"#),
740 #[cfg(feature = "ip")]
741 ("ip", r#"Ip("0.0.0.0/0").contains(Ip("127.0.0.1"))"#),
742 #[cfg(feature = "ip")]
743 ("ip does not contain", r#"!Ip("0.0.0.0/32").contains(Ip("127.0.0.1"))"#),
744 ];
745
746 for (name, script) in tests {
747 assert_eq!(test_script(script, None), Ok(true.into()), "{name}");
748 }
749 }
750
751 #[cfg(feature = "regex")]
752 #[test]
753 fn test_matches() {
754 let tests = vec![
755 ("string", r#""foobar".matches(Regex("^[a-zA-Z]*$")) == true"#),
756 (
757 "map",
758 r#"{"1": "abc", "2": "def", "3": "ghi"}.all(key, key.matches(Regex("^[a-zA-Z]*$"))) == false"#,
759 ),
760 ];
761
762 for (name, script) in tests {
763 assert_eq!(test_script(script, None), Ok(true.into()), ".matches failed for '{name}'");
764 }
765 }
766
767 #[cfg(feature = "regex")]
768 #[test]
769 fn test_regex_err() {
770 assert_eq!(
771 test_script(r#""foobar".matches(Regex("(foo")) == true"#, None),
772 Err(crate::ExecutionError::FunctionError {
773 function: "Regex".to_string(),
774 message: "regex parse error:\n (foo\n ^\nerror: unclosed group".to_string()
776 })
777 );
778 }
779
780 #[test]
781 fn test_string() {
782 [
783 ("String", r#"String("foo") == "foo""#),
784 ("Int", r#"String(10) == "10""#),
785 ("Float", r#"String(10.5) == "10.5""#),
786 ("Bytes", r#"String(b"foo") == "foo""#),
787 ]
788 .iter()
789 .for_each(assert_script);
790 }
791
792 #[test]
793 fn test_bytes() {
794 [
795 ("String", r#"Bytes("abc") == b"abc""#),
796 ("Bytes", r#"Bytes("abc") == b"\x61b\x63""#),
797 ]
798 .iter()
799 .for_each(assert_script);
800 }
801
802 #[test]
803 fn test_float() {
804 [
805 ("String", r#"Float("10") == 10.0"#),
806 ("Int", "Float(10)== 10.0"),
807 ("Float", "Float(10) == 10.0"),
808 ]
809 .iter()
810 .for_each(assert_script);
811 }
812
813 #[test]
824 fn test_int() {
825 [
826 ("String", r#"Int("10") == 10"#),
827 ("Int", "Int(10) == 10"),
828 ("Float", "Int(10.5) == 10"),
830 ]
831 .iter()
832 .for_each(assert_script);
833 }
834
835 #[test]
836 fn no_bool_coercion() {
837 [
838 ("String || bool", r#""" || false"#, "No such overload"),
839 ("Int || bool", "1 || false", "No such overload"),
840 ("Float || bool", "0.1|| false", "No such overload"),
842 ("List || bool", "[] || false", "No such overload"),
843 ("Map || bool", "{} || false", "No such overload"),
844 ("null || bool", "null || false", "No such overload"),
845 ]
846 .iter()
847 .for_each(assert_error)
848 }
849}