tera_v1/builtins/
functions.rs

1use std::collections::HashMap;
2
3use chrono::prelude::*;
4use serde_json::value::{from_value, to_value, Value};
5
6use crate::errors::{Error, Result};
7
8/// The global function type definition
9pub trait Function: Sync + Send {
10    /// The global function type definition
11    fn call(&self, args: &HashMap<String, Value>) -> Result<Value>;
12}
13
14impl<F> Function for F
15where
16    F: Fn(&HashMap<String, Value>) -> Result<Value> + Sync + Send,
17{
18    fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
19        self(args)
20    }
21}
22
23pub fn range(args: &HashMap<String, Value>) -> Result<Value> {
24    let start = match args.get("start") {
25        Some(val) => match from_value::<usize>(val.clone()) {
26            Ok(v) => v,
27            Err(_) => {
28                return Err(Error::msg(format!(
29                    "Global function `range` received start={} but `start` can only be a number",
30                    val
31                )));
32            }
33        },
34        None => 0,
35    };
36    let step_by = match args.get("step_by") {
37        Some(val) => match from_value::<usize>(val.clone()) {
38            Ok(v) => v,
39            Err(_) => {
40                return Err(Error::msg(format!(
41                    "Global function `range` received step_by={} but `step` can only be a number",
42                    val
43                )));
44            }
45        },
46        None => 1,
47    };
48    let end = match args.get("end") {
49        Some(val) => match from_value::<usize>(val.clone()) {
50            Ok(v) => v,
51            Err(_) => {
52                return Err(Error::msg(format!(
53                    "Global function `range` received end={} but `end` can only be a number",
54                    val
55                )));
56            }
57        },
58        None => {
59            return Err(Error::msg("Global function `range` was called without a `end` argument"));
60        }
61    };
62
63    if start > end {
64        return Err(Error::msg("Global function `range` was called without a `start` argument greater than the `end` one"));
65    }
66
67    let mut i = start;
68    let mut res = vec![];
69    while i < end {
70        res.push(i);
71        i += step_by;
72    }
73    Ok(to_value(res).unwrap())
74}
75
76pub fn now(args: &HashMap<String, Value>) -> Result<Value> {
77    let utc = match args.get("utc") {
78        Some(val) => match from_value::<bool>(val.clone()) {
79            Ok(v) => v,
80            Err(_) => {
81                return Err(Error::msg(format!(
82                    "Global function `now` received utc={} but `utc` can only be a boolean",
83                    val
84                )));
85            }
86        },
87        None => false,
88    };
89    let timestamp = match args.get("timestamp") {
90        Some(val) => match from_value::<bool>(val.clone()) {
91            Ok(v) => v,
92            Err(_) => {
93                return Err(Error::msg(format!(
94                "Global function `now` received timestamp={} but `timestamp` can only be a boolean",
95                val
96            )));
97            }
98        },
99        None => false,
100    };
101
102    if utc {
103        let datetime = Utc::now();
104        if timestamp {
105            return Ok(to_value(datetime.timestamp()).unwrap());
106        }
107        Ok(to_value(datetime.to_rfc3339()).unwrap())
108    } else {
109        let datetime = Local::now();
110        if timestamp {
111            return Ok(to_value(datetime.timestamp()).unwrap());
112        }
113        Ok(to_value(datetime.to_rfc3339()).unwrap())
114    }
115}
116
117pub fn throw(args: &HashMap<String, Value>) -> Result<Value> {
118    match args.get("message") {
119        Some(val) => match from_value::<String>(val.clone()) {
120            Ok(v) => Err(Error::msg(v)),
121            Err(_) => Err(Error::msg(format!(
122                "Global function `throw` received message={} but `message` can only be a string",
123                val
124            ))),
125        },
126        None => Err(Error::msg("Global function `throw` was called without a `message` argument")),
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use std::collections::HashMap;
133
134    use serde_json::value::to_value;
135
136    use super::*;
137
138    #[test]
139    fn range_default() {
140        let mut args = HashMap::new();
141        args.insert("end".to_string(), to_value(5).unwrap());
142
143        let res = range(&args).unwrap();
144        assert_eq!(res, to_value(vec![0, 1, 2, 3, 4]).unwrap());
145    }
146
147    #[test]
148    fn range_start() {
149        let mut args = HashMap::new();
150        args.insert("end".to_string(), to_value(5).unwrap());
151        args.insert("start".to_string(), to_value(1).unwrap());
152
153        let res = range(&args).unwrap();
154        assert_eq!(res, to_value(vec![1, 2, 3, 4]).unwrap());
155    }
156
157    #[test]
158    fn range_start_greater_than_end() {
159        let mut args = HashMap::new();
160        args.insert("end".to_string(), to_value(5).unwrap());
161        args.insert("start".to_string(), to_value(6).unwrap());
162
163        assert!(range(&args).is_err());
164    }
165
166    #[test]
167    fn range_step_by() {
168        let mut args = HashMap::new();
169        args.insert("end".to_string(), to_value(10).unwrap());
170        args.insert("step_by".to_string(), to_value(2).unwrap());
171
172        let res = range(&args).unwrap();
173        assert_eq!(res, to_value(vec![0, 2, 4, 6, 8]).unwrap());
174    }
175
176    #[test]
177    fn now_default() {
178        let args = HashMap::new();
179
180        let res = now(&args).unwrap();
181        assert!(res.is_string());
182        assert!(res.as_str().unwrap().contains("T"));
183    }
184
185    #[test]
186    fn now_datetime_utc() {
187        let mut args = HashMap::new();
188        args.insert("utc".to_string(), to_value(true).unwrap());
189
190        let res = now(&args).unwrap();
191        assert!(res.is_string());
192        let val = res.as_str().unwrap();
193        println!("{}", val);
194        assert!(val.contains("T"));
195        assert!(val.contains("+00:00"));
196    }
197
198    #[test]
199    fn now_timestamp() {
200        let mut args = HashMap::new();
201        args.insert("timestamp".to_string(), to_value(true).unwrap());
202
203        let res = now(&args).unwrap();
204        assert!(res.is_number());
205    }
206
207    #[test]
208    fn throw_errors_with_message() {
209        let mut args = HashMap::new();
210        args.insert("message".to_string(), to_value("Hello").unwrap());
211
212        let res = throw(&args);
213        assert!(res.is_err());
214        let err = res.unwrap_err();
215        assert_eq!(err.to_string(), "Hello");
216    }
217}