eval_utility/
lib.rs

1#![doc = include_str ! ("./../README.md")]
2#![forbid(unsafe_code)]
3
4pub mod types {
5    pub type Expr = resolver::Expr;
6    pub type Value = resolver::Value;
7
8    pub fn to_value<S: serde::Serialize>(v: S) -> Value {
9        resolver::to_value(v)
10    }
11}
12
13pub mod template {
14    use lazy_static::lazy_static;
15    use regex::Regex;
16
17    use crate::types::*;
18
19    lazy_static! {
20        static ref CONDITION_PATTERN: Regex = Regex::new(r"(<\?([^\?]*)\?>)").unwrap();
21        static ref CONTEXT_SYM: String = String::from("$");
22    }
23
24    pub fn resolve_template(
25        template: String,
26        context: Value,
27    ) -> Result<String, resolver::Error> {
28        let mut map = std::collections::HashMap::<String, String>::new();
29        for cap in CONDITION_PATTERN.captures_iter(&*template) {
30            let a = &cap[1];
31            let b = cap[2].trim();
32            if !b.is_empty() {
33                let mut expr = Expr::new(b)
34                    .value(CONTEXT_SYM.to_string(), &context);
35                let value = expr.exec()?;
36                let value_str = match value {
37                    Value::Null => "null".into(),
38                    Value::Bool(boolean) => boolean.to_string(),
39                    Value::Number(number) => number.to_string(),
40                    Value::String(string) => string,
41                    Value::Array(arr) => serde_json::to_string(&arr)
42                        .unwrap_or_else(|_| "null".into()),
43                    Value::Object(obj) => serde_json::to_string(&obj)
44                        .unwrap_or_else(|_| "null".into())
45                };
46                map.insert(a.to_string(), value_str);
47            } else {
48                map.insert(a.to_string(), "".into());
49            }
50        }
51
52        let mut result = template;
53        for (key, value) in map.iter() {
54            // TODO(Dustin): Replace by range?
55            result = result.replace(key, value);
56        }
57
58        Ok(result)
59    }
60}
61
62pub mod eval_wrapper {
63    use chrono::{Datelike, Timelike};
64    use resolver::{to_value, Expr};
65    use regex::Regex;
66    // use inflection_rs::inflection;
67    use string_utility::prelude::*;
68
69    use crate::types::*;
70
71    #[derive(Debug, Clone)]
72    pub struct EvalConfig {
73        pub include_maths: bool,
74        pub include_datetime: bool,
75        pub include_cast: bool,
76        pub include_regex: bool,
77    }
78
79    impl EvalConfig {
80        pub fn any(&self) -> bool {
81            self.include_maths
82                || self.include_datetime
83                || self.include_cast
84                || self.include_regex
85        }
86    }
87
88    impl Default for EvalConfig {
89        fn default() -> Self {
90            Self {
91                include_maths: true,
92                include_datetime: true,
93                include_cast: true,
94                include_regex: true,
95            }
96        }
97    }
98
99    fn value_to_string(val: &Value) -> String {
100        match val {
101            Value::Number(x) => x.as_f64().unwrap().to_string(),
102            Value::Bool(x) => x.to_string(),
103            Value::String(x) => x.to_string(),
104            Value::Array(x) => serde_json::to_string(x)
105                .unwrap_or_else(|_| "null".into()),
106            Value::Object(x) => serde_json::to_string(x)
107                .unwrap_or_else(|_| "null".into()),
108            _ => String::from("null"),
109        }
110    }
111
112    pub fn math_consts() -> Value {
113        serde_json::json!{{
114            "MIN_INT": i64::MIN,
115            "MAX_INT": i64::MAX,
116            "MAX_FLOAT": f64::MAX,
117            "MIN_FLOAT": f64::MIN,
118            "INC": f64::NAN,
119            "NOT_A_NUMBER": f64::NAN,
120            "INFINITE": f64::INFINITY,
121            "NEG_INFINITE": f64::NEG_INFINITY,
122            "E": std::f64::consts::E,
123            "FRAC_1_SQRT_2": std::f64::consts::FRAC_1_SQRT_2,
124            "FRAC_2_SQRT_PI": std::f64::consts::FRAC_2_SQRT_PI,
125            "FRAC_1_PI": std::f64::consts::FRAC_1_PI,
126            "FRAC_PI_2": std::f64::consts::FRAC_PI_2,
127            "FRAC_PI_3": std::f64::consts::FRAC_PI_3,
128            "FRAC_PI_4": std::f64::consts::FRAC_PI_4,
129            "FRAC_PI_6": std::f64::consts::FRAC_PI_6,
130            "FRAC_PI_8": std::f64::consts::FRAC_PI_8,
131            "LN_2": std::f64::consts::LN_2,
132            "LN_10": std::f64::consts::LN_10,
133            "LOG2_10": std::f64::consts::LOG2_10,
134            "LOG2_E": std::f64::consts::LOG2_E,
135            "LOG10_2": std::f64::consts::LOG10_2,
136            "LOG10_E": std::f64::consts::LOG10_E,
137            "PI": std::f64::consts::PI,
138            "SQRT_2": std::f64::consts::SQRT_2,
139            "TAU": std::f64::consts::TAU,
140        }}
141    }
142
143    #[derive(Clone)]
144    pub struct ExprWrapper {
145        expr: Expr,
146        config: EvalConfig,
147    }
148
149    impl ExprWrapper {
150        pub fn new<S: AsRef<str>>(expression: S) -> ExprWrapper {
151            ExprWrapper {
152                expr: Expr::new(expression.as_ref()),
153                config: Default::default(),
154            }
155        }
156
157        pub fn config(mut self, config: EvalConfig) -> ExprWrapper {
158            self.config = config;
159            self
160        }
161
162        pub fn init(mut self) -> ExprWrapper {
163            self.expr = expr_wrapper(self.expr.clone(), self.config.clone());
164            self
165        }
166
167        pub fn value<T, V>(mut self, name: T, value: V) -> ExprWrapper
168            where T: Into<String>,
169                  V: serde::Serialize
170        {
171            self.expr = self.expr.value(name, value);
172            self
173        }
174
175        pub fn function<T, F>(mut self, name: T, function: F) -> ExprWrapper
176            where T: Into<String>,
177                  F: 'static + Fn(Vec<Value>) -> Result<Value, resolver::Error> + Sync + Send
178        {
179            self.expr = self.expr.function(name, function);
180            self
181        }
182
183        pub fn exec(&mut self) -> Result<Value, resolver::Error> {
184            self.expr.exec()
185        }
186    }
187
188    /// This function is DEPRECATED see README.md for new usage.
189    #[deprecated]
190    pub fn expr_wrapper(exp: Expr, config: EvalConfig) -> Expr {
191        if !config.any() {
192            return exp;
193        }
194
195        let mut result = exp;
196
197        if config.include_cast {
198            result = result
199                .function("int", |value| {
200                    if value.is_empty() {
201                        return Ok(to_value(0_i64));
202                    }
203                    let v = match value.get(0) {
204                        None => to_value(0),
205                        Some(value) => value.to_owned(),
206                    };
207
208                    let num: i64 = match v {
209                        Value::Number(x) => {
210                            if x.is_f64() {
211                                x.as_f64().unwrap_or(0_f64) as i64
212                            } else {
213                                x.as_i64().unwrap_or(0)
214                            }
215                        }
216                        Value::Bool(x) => {
217                            if x {
218                                1
219                            } else {
220                                0
221                            }
222                        }
223                        Value::String(x) => atoi(x),
224                        _ => 0,
225                    };
226                    Ok(to_value(num))
227                })
228                .function("float", |value| {
229                    if value.is_empty() {
230                        return Ok(to_value(f64::NAN));
231                    }
232                    let v = match value.get(0) {
233                        None => to_value(0_f64),
234                        Some(value) => value.to_owned(),
235                    };
236                    let num: f64 = match v {
237                        Value::Number(x) => x.as_f64().unwrap_or(0_f64),
238                        Value::Bool(x) => {
239                            if x {
240                                1.0
241                            } else {
242                                0.0
243                            }
244                        }
245                        Value::String(x) => match x.parse::<f64>() {
246                            Ok(x) => x,
247                            _ => f64::NAN,
248                        },
249                        _ => f64::NAN,
250                    };
251
252                    Ok(to_value(num))
253                })
254                .function("bool", |value| {
255                    if value.is_empty() {
256                        return Ok(to_value(false));
257                    }
258                    let v = match value.get(0) {
259                        None => to_value(false),
260                        Some(value) => value.to_owned(),
261                    };
262
263                    let result: bool = match v {
264                        Value::Number(x) => x.as_f64().unwrap_or(0_f64) != 0.0,
265                        Value::Bool(x) => x,
266                        Value::String(x) => !x.is_empty(),
267                        Value::Array(x) => !x.is_empty(),
268                        Value::Object(x) => !x.is_empty(),
269                        _ => false,
270                    };
271
272                    Ok(to_value(result))
273                })
274                .function("str", |value| {
275                    if value.is_empty() {
276                        return Ok(to_value("".to_string()));
277                    }
278                    let v = match value.get(0) {
279                        None => to_value("".to_string()),
280                        Some(value) => value.to_owned(),
281                    };
282
283                    let result: String = match v {
284                        Value::Number(x) => {
285                            if x.is_f64() {
286                                x.as_f64().unwrap_or(0_f64).to_string()
287                            } else {
288                                x.as_i64().unwrap_or(0_i64).to_string()
289                            }
290                        }
291                        Value::Bool(x) => x.to_string(),
292                        Value::String(x) => x,
293                        Value::Array(x) => serde_json::to_string(&x)
294                            .unwrap_or_else(|_| "null".to_string()),
295                        Value::Object(x) => serde_json::to_string(&x)
296                            .unwrap_or_else(|_| "null".to_string()),
297                        _ => "null".to_string(),
298                    };
299                    Ok(to_value(result))
300                });
301        }
302
303        if config.include_maths {
304            result = result
305                .value("maths", math_consts())
306                .value("NAN", to_value(f64::NAN))
307                .value("INFINITY", to_value(f64::INFINITY))
308                .value("NEG_INFINITY", to_value(f64::NEG_INFINITY));
309        }
310
311        if config.include_regex {
312            result = result.function("is_match", |value| {
313                if value.len() < 2 {
314                    return Ok(to_value(false));
315                }
316
317                let v = value.get(0).unwrap();
318                let pattern = value.get(1).unwrap().as_str().unwrap();
319
320                let value: String = value_to_string(v);
321
322                let prog = Regex::new(pattern).unwrap();
323                let is_match = prog.is_match(&value);
324                Ok(to_value(is_match))
325            }).function("extract", |value| {
326                if value.len() < 2 {
327                    return Ok(to_value(false));
328                }
329
330                let v = value
331                    .get(0).expect("missing first positional argument (string)");
332                let pattern = value
333                    .get(1).expect("missing second positional argument (pattern)")
334                    .as_str().expect("second positional arguments needs to be a string");
335
336                let value: String = value_to_string(v);
337                let prog = Regex::new(pattern).unwrap();
338                match prog.find(&value) {
339                    None => Ok(to_value("".to_string())),
340                    Some(m) => {
341                        let (start, end) = (m.start(), m.end());
342                        Ok(to_value(value.substring(start..end)))
343                    }
344                }
345            });
346        }
347
348        if config.include_datetime {
349            result = result
350                .function("get_day", |values| {
351                    let current_time = eval_tz_parse_args(values, 1);
352                    Ok(to_value(current_time.date_naive().day()))
353                })
354                .function("get_month", |values| {
355                    let current_time = eval_tz_parse_args(values, 1);
356                    Ok(to_value(current_time.date_naive().month()))
357                })
358                .function("get_year", |values| {
359                    let current_time = eval_tz_parse_args(values, 1);
360                    Ok(to_value(current_time.date_naive().year()))
361                })
362                .function("get_weekday", |values| {
363                    let current_time = eval_tz_parse_args(values, 1);
364                    Ok(to_value(
365                        current_time.date_naive().weekday().number_from_monday(),
366                    ))
367                })
368                .function("is_weekday", |values| {
369                    let current_time = eval_tz_parse_args(values, 1);
370                    let weekday = current_time.date_naive().weekday().number_from_monday();
371                    Ok(to_value(weekday < 6))
372                })
373                .function("is_weekend", |values| {
374                    let current_time = eval_tz_parse_args(values, 1);
375                    let weekday = current_time.date_naive().weekday();
376                    let weekends = [chrono::Weekday::Sat, chrono::Weekday::Sun];
377                    Ok(to_value(weekends.contains(&weekday)))
378                })
379                .function("get_time", |extract| {
380                    if extract.len() < 2 {
381                        let t = now("_".to_owned());
382                        return Ok(to_value(t.hour()));
383                    }
384
385                    let v: String = match extract.get(1).unwrap() {
386                        Value::Number(x) => {
387                            if x.is_f64() {
388                                x.as_f64().unwrap().to_string()
389                            } else if x.is_i64() {
390                                x.as_i64().unwrap().to_string()
391                            } else if x.is_u64() {
392                                x.as_u64().unwrap().to_string()
393                            } else {
394                                x.to_string()
395                            }
396                        }
397                        Value::Bool(x) => x.to_string(),
398                        Value::String(x) => x.to_string(),
399                        Value::Array(x) => serde_json::to_string(x).unwrap(),
400                        Value::Object(x) => serde_json::to_string(x).unwrap(),
401                        _ => String::from("null"),
402                    };
403
404                    let dt = eval_tz_parse_args(extract, 2);
405                    let current_time = dt.time();
406
407                    let result = match v.as_str() {
408                        "h" | "hour" | "hours" => current_time.hour(),
409                        "m" | "minute" | "minutes" => current_time.minute(),
410                        "s" | "second" | "seconds" => current_time.second(),
411                        _ => current_time.hour(),
412                    };
413                    Ok(to_value(result))
414                });
415        }
416
417        result
418
419        // TODO: is_nan(n), is_min_int(n), is_int_max(n), includes(arr)
420        // TODO: min(arr), max(arr), abs(n), pow(n, p), sum(arr), reverse(arr), sort(arr), unique(arr)
421    }
422
423    fn eval_tz_parse_args(
424        arguments: Vec<Value>,
425        min_args: usize,
426    ) -> chrono::DateTime<chrono_tz::Tz> {
427        let default_tz = "_".to_owned();
428        if arguments.is_empty() || arguments.len() < min_args {
429            log::warn!("No arguments");
430            return now(default_tz);
431        }
432
433        let v: Option<String> = match arguments.get(0).unwrap() {
434            Value::String(x) => Some(x.to_string()),
435            _ => None,
436        };
437
438        match v {
439            None => {
440                log::warn!("Invalid Timezone");
441                now(default_tz)
442            }
443            Some(timezone) => now(timezone)
444        }
445    }
446
447    fn now(tz: String) -> chrono::DateTime<chrono_tz::Tz> {
448        chrono::offset::Utc::now()
449            .with_timezone(&str_to_tz(tz))
450    }
451
452    fn str_to_tz(timezone: String) -> chrono_tz::Tz {
453        match timezone.parse() {
454            Ok(tz) => tz,
455            Err(_err) => {
456                log::warn!("Defaulted to UTC timezone");
457                chrono_tz::UTC
458            }
459        }
460    }
461
462    fn atoi(s: String) -> i64 {
463        let mut item = s
464            .trim()
465            .split(char::is_whitespace)
466            .next()
467            .unwrap_or("")
468            .split(char::is_alphabetic)
469            .next()
470            .unwrap_or("");
471
472        let mut end_idx = 0;
473        for (pos, c) in item.chars().enumerate() {
474            if pos == 0 {
475                continue;
476            }
477
478            if !c.is_alphanumeric() {
479                end_idx = pos;
480                break;
481            }
482        }
483
484        if end_idx > 0 {
485            item = &item[0..end_idx];
486        }
487
488        let result = item.parse::<i64>();
489        match result {
490            Ok(v) => v,
491            Err(error) => match error.kind() {
492                std::num::IntErrorKind::NegOverflow => i64::MIN,
493                std::num::IntErrorKind::PosOverflow => i64::MAX,
494                std::num::IntErrorKind::InvalidDigit => {
495                    let result = item.parse::<f64>();
496                    match result {
497                        Ok(v) => v.round() as i64,
498                        _ => 0,
499                    }
500                }
501                _ => 0,
502            },
503        }
504    }
505}
506
507
508#[cfg(test)]
509mod eval {
510    use chrono::offset::Utc as Date;
511    use chrono::{Datelike, Timelike};
512    use resolver::to_value;
513    use serde_json::json;
514
515    use crate::{eval_wrapper::{EvalConfig, ExprWrapper}, template};
516
517    #[derive(Default)]
518    struct Spec;
519
520    impl Spec {
521        pub fn eval<S: AsRef<str>>(&self, expression: S) -> resolver::Value {
522            let mut expr = ExprWrapper::new(expression.as_ref())
523                .config(EvalConfig {
524                    include_maths: true,
525                    include_regex: true,
526                    include_datetime: true,
527                    include_cast: true,
528                })
529                .init();
530            let result = expr.exec();
531
532            if result.is_err() {
533                panic!(
534                    "Failed to parse expression: \"{}\" {:?}",
535                    expression.as_ref(),
536                    result
537                )
538            }
539
540            result.unwrap()
541        }
542    }
543
544    #[test]
545    fn maths_consts() {
546        let user_spec = Spec::default();
547        assert_eq!(user_spec.eval("NAN"), to_value(f64::NAN));
548        assert_eq!(user_spec.eval("INFINITY"), to_value(f64::INFINITY));
549        assert_eq!(user_spec.eval("NEG_INFINITY"), to_value(f64::NEG_INFINITY));
550        assert_eq!(user_spec.eval("maths.MAX_INT"), to_value(i64::MAX));
551        assert_eq!(user_spec.eval("maths.MAX_FLOAT"), to_value(f64::MAX));
552        assert_eq!(user_spec.eval("maths.MIN_FLOAT"), to_value(f64::MIN));
553        assert_eq!(user_spec.eval("maths.INC"), to_value(f64::NAN));
554        assert_eq!(user_spec.eval("maths.NOT_A_NUMBER"), to_value(f64::NAN));
555        assert_eq!(user_spec.eval("maths.INFINITE"), to_value(f64::INFINITY));
556        assert_eq!(user_spec.eval("maths.NEG_INFINITE"), to_value(f64::NEG_INFINITY));
557        assert_eq!(user_spec.eval("maths.E"), to_value(std::f64::consts::E));
558        assert_eq!(user_spec.eval("maths.FRAC_1_SQRT_2"), to_value(std::f64::consts::FRAC_1_SQRT_2));
559        assert_eq!(user_spec.eval("maths.FRAC_2_SQRT_PI"), to_value(std::f64::consts::FRAC_2_SQRT_PI));
560        assert_eq!(user_spec.eval("maths.FRAC_1_PI"), to_value(std::f64::consts::FRAC_1_PI));
561        assert_eq!(user_spec.eval("maths.FRAC_PI_2"), to_value(std::f64::consts::FRAC_PI_2));
562        assert_eq!(user_spec.eval("maths.FRAC_PI_3"), to_value(std::f64::consts::FRAC_PI_3));
563        assert_eq!(user_spec.eval("maths.FRAC_PI_4"), to_value(std::f64::consts::FRAC_PI_4));
564        assert_eq!(user_spec.eval("maths.FRAC_PI_6"), to_value(std::f64::consts::FRAC_PI_6));
565        assert_eq!(user_spec.eval("maths.FRAC_PI_8"), to_value(std::f64::consts::FRAC_PI_8));
566        assert_eq!(user_spec.eval("maths.LN_2"), to_value(std::f64::consts::LN_2));
567        assert_eq!(user_spec.eval("maths.LN_10"), to_value(std::f64::consts::LN_10));
568        assert_eq!(user_spec.eval("maths.LOG2_10"), to_value(std::f64::consts::LOG2_10));
569        assert_eq!(user_spec.eval("maths.LOG2_E"), to_value(std::f64::consts::LOG2_E));
570        assert_eq!(user_spec.eval("maths.LOG10_2"), to_value(std::f64::consts::LOG10_2));
571        assert_eq!(user_spec.eval("maths.LOG10_E"), to_value(std::f64::consts::LOG10_E));
572        assert_eq!(user_spec.eval("maths.PI"), to_value(std::f64::consts::PI));
573        assert_eq!(user_spec.eval("maths.SQRT_2"), to_value(std::f64::consts::SQRT_2));
574        assert_eq!(user_spec.eval("maths.TAU"), to_value(std::f64::consts::TAU));
575    }
576
577    #[test]
578    fn literal() {
579        let user_spec = Spec::default();
580
581        assert_eq!(user_spec.eval("42"), 42);
582        assert_eq!(user_spec.eval("0-42"), -42);
583        assert_eq!(user_spec.eval("true"), true);
584        assert_eq!(user_spec.eval("false"), false);
585        assert_eq!(user_spec.eval("\"42\""), "42");
586        assert_eq!(user_spec.eval("'42'"), "42");
587        assert_eq!(user_spec.eval("array(42, 42)"), to_value(vec![42; 2]));
588        assert_eq!(user_spec.eval("array()"), to_value(vec![0; 0]));
589        assert_eq!(user_spec.eval("0..5"), to_value(vec![0, 1, 2, 3, 4]));
590    }
591
592    #[test]
593    fn _str() {
594        let user_spec = Spec::default();
595        assert_eq!(user_spec.eval("str(42)"), "42");
596        assert_eq!(user_spec.eval("str(42.42)"), "42.42");
597        assert_eq!(user_spec.eval("str(true)"), "true");
598        assert_eq!(user_spec.eval("str(array(42, 42))"), to_value("[42,42]"));
599        assert_eq!(user_spec.eval("str(array())"), to_value("[]"));
600        assert_eq!(user_spec.eval("str(null)"), to_value("null"));
601    }
602
603    #[test]
604    fn bool() {
605        let user_spec = Spec::default();
606
607        assert_eq!(user_spec.eval("bool(1)"), true);
608        assert_eq!(user_spec.eval("bool(1.0)"), true);
609        assert_eq!(user_spec.eval("bool(0)"), false);
610        assert_eq!(user_spec.eval("bool(0.0)"), false);
611        assert_eq!(user_spec.eval("bool(true)"), true);
612        assert_eq!(user_spec.eval("bool(false)"), false);
613
614        assert_eq!(user_spec.eval("bool(42)"), true);
615        assert_eq!(user_spec.eval("bool(42.42)"), true);
616        assert_eq!(user_spec.eval("bool(0-42)"), true);
617        assert_eq!(user_spec.eval("bool(0-42.42)"), true);
618
619        assert_eq!(user_spec.eval("bool('')"), false);
620        assert_eq!(user_spec.eval("bool(\"\")"), false);
621        assert_eq!(user_spec.eval("bool('42')"), true);
622        assert_eq!(user_spec.eval("bool(\"42\")"), true);
623
624        assert_eq!(user_spec.eval("bool(array(42, 42))"), true);
625        assert_eq!(user_spec.eval("bool(array())"), false);
626        assert_eq!(user_spec.eval("bool(0..42)"), true);
627        assert_eq!(user_spec.eval("bool(0..0)"), false);
628        assert_eq!(user_spec.eval("bool(null)"), false);
629    }
630
631    #[test]
632    fn float() {
633        let user_spec = Spec::default();
634        assert_eq!(user_spec.eval("float(42)"), 42.0);
635        assert_eq!(user_spec.eval("float(42.42)"), 42.42);
636        assert_eq!(user_spec.eval("float('42.42')"), 42.42);
637        assert_eq!(user_spec.eval("float('42')"), 42.0);
638        assert_eq!(user_spec.eval("float(true)"), 1.0);
639        assert_eq!(user_spec.eval("float(false)"), 0.0);
640        assert_eq!(user_spec.eval("float('')"), to_value(f64::NAN));
641        assert_eq!(
642            user_spec.eval("float('not a num')"),
643            to_value(f64::NAN)
644        );
645        assert_eq!(user_spec.eval("float(ctx)"), to_value(f64::NAN));
646        assert_eq!(
647            user_spec.eval("float(array(42, 42))"),
648            to_value(f64::NAN)
649        );
650        assert_eq!(user_spec.eval("float(0..42)"), to_value(f64::NAN));
651        assert_eq!(user_spec.eval("float(null)"), to_value(f64::NAN));
652    }
653
654    #[test]
655    fn int() {
656        let user_spec = Spec::default();
657        assert_eq!(user_spec.eval("int(42)"), 42);
658        assert_eq!(user_spec.eval("int(42.42)"), 42);
659        assert_eq!(user_spec.eval("int('42.42')"), 42);
660        assert_eq!(user_spec.eval("int('42')"), 42);
661        assert_eq!(user_spec.eval("int(true)"), 1);
662        assert_eq!(user_spec.eval("int(false)"), 0);
663        assert_eq!(user_spec.eval("int('')"), 0);
664        assert_eq!(user_spec.eval("int('not a num')"), 0);
665        assert_eq!(user_spec.eval("int(ctx)"), 0);
666        assert_eq!(user_spec.eval("int(array(42, 42))"), 0);
667        assert_eq!(user_spec.eval("int(0..42)"), 0);
668        assert_eq!(user_spec.eval("int(null)"), 0);
669    }
670
671    #[test]
672    fn day() {
673        let user_spec = Spec::default();
674        let date = Date::now().date_naive();
675        let day = date.day();
676
677        assert_eq!(user_spec.eval("get_day()"), day);
678        assert_eq!(user_spec.eval("get_day('_')"), day);
679    }
680
681    #[test]
682    fn month() {
683        let user_spec = Spec::default();
684        let date = Date::now().date_naive();
685        let month = date.month();
686
687        assert_eq!(user_spec.eval("get_month()"), month);
688        assert_eq!(user_spec.eval("get_month('_')"), month);
689    }
690
691    #[test]
692    fn year() {
693        let user_spec = Spec::default();
694        let date = Date::now().date_naive();
695        let year = date.year();
696        assert_eq!(user_spec.eval("get_year()"), year);
697        assert_eq!(user_spec.eval("get_year('_')"), year);
698    }
699
700    #[test]
701    fn weekday() {
702        let user_spec = Spec::default();
703        let weekday_num = Date::now().weekday().number_from_monday();
704        assert_eq!(user_spec.eval("get_weekday('_')"), weekday_num);
705        assert_eq!(user_spec.eval("is_weekday('_')"), weekday_num < 6);
706
707        assert_eq!(user_spec.eval("get_weekday()"), weekday_num);
708        assert_eq!(user_spec.eval("is_weekday()"), weekday_num < 6);
709    }
710
711    #[test]
712    fn time() {
713        let user_spec = Spec::default();
714        assert_eq!(user_spec.eval("get_time('_', 'h')"), Date::now().time().hour());
715        assert_eq!(user_spec.eval("get_time('_', 'm')"), Date::now().time().minute());
716        assert_eq!(user_spec.eval("get_time('_', 's')"), Date::now().time().second());
717
718        assert_eq!(user_spec.eval("get_time('_', 'hour')"), Date::now().time().hour());
719        assert_eq!(
720            user_spec.eval("get_time('_', 'minute')"),
721            Date::now().time().minute()
722        );
723        assert_eq!(
724            user_spec.eval("get_time('_', 'second')"),
725            Date::now().time().second()
726        );
727
728        assert_eq!(user_spec.eval("get_time('_', 'hours')"), Date::now().time().hour());
729        assert_eq!(user_spec.eval("get_time()"), Date::now().time().hour());
730        assert_eq!(
731            user_spec.eval("get_time('_', 'minutes')"),
732            Date::now().time().minute()
733        );
734        assert_eq!(
735            user_spec.eval("get_time('_', 'seconds')"),
736            Date::now().time().second()
737        );
738    }
739
740    #[test]
741    fn is_match() {
742        let user_spec = Spec::default();
743        assert_eq!(user_spec.eval("is_match('http', '^https?$')"), to_value(true));
744        assert_eq!(user_spec.eval("is_match('http', 'https')"), to_value(false));
745        assert_eq!(user_spec.eval("is_match('http://', '^udp://')"), to_value(false));
746        assert_eq!(user_spec.eval("is_match('http://', '^(https?|wss?)://$')"), to_value(true));
747        assert_eq!(user_spec.eval(r"is_match('2014-01-01', '^\d{4}-\d{2}-\d{2}$')"), to_value(true));
748    }
749
750    #[test]
751    fn extract() {
752        let user_spec = Spec::default();
753        assert_eq!(user_spec.eval("extract('http://www.floa', 'https?://')"), "http://");
754        assert_eq!(user_spec.eval("extract('foo', 'bar')"), "");
755    }
756
757    #[test]
758    fn template_engine() {
759        let context = json! {{
760            "name": "Kar",
761            "location": "foo-bar",
762            "some": {
763                "deep": {
764                    "value": 42
765                }
766            }
767        }};
768
769        assert_eq!(
770            template::resolve_template(
771                "Hi, my name is <? $.name ?> and I live in <? $.location ?> <? $.some.deep.value ?>".to_string(),
772                context.clone(),
773            ).expect("Failed to resolve template"),
774            "Hi, my name is Kar and I live in foo-bar 42".to_string()
775        );
776
777        assert_eq!(
778            template::resolve_template(
779                "Hi, my name is Kar and I live in foo-bar 42".to_string(),
780                context.clone(),
781            ).expect("Failed to resolve template"),
782            "Hi, my name is Kar and I live in foo-bar 42".to_string()
783        );
784
785        assert_eq!(
786            template::resolve_template(
787                "".to_string(),
788                context.clone(),
789            ).expect("Failed to resolve template"),
790            "".to_string()
791        );
792
793        assert_eq!(
794            template::resolve_template(
795                "Hello, <? ?>".to_string(),
796                context,
797            ).expect("Failed to resolve template"),
798            "Hello, ".to_string(),
799        );
800    }
801}