archetect_core/vendor/tera/builtins/
functions.rs

1use std::collections::HashMap;
2
3use chrono::prelude::*;
4#[cfg(feature = "builtins")]
5use rand::Rng;
6use serde_json::value::{from_value, to_value, Value};
7
8use crate::vendor::tera::errors::{Error, Result};
9
10/// The global function type definition
11pub trait Function: Sync + Send {
12    /// The global function type definition
13    fn call(&self, args: &HashMap<String, Value>) -> Result<Value>;
14
15    /// Whether the current function's output should be treated as safe, defaults to `false`
16    fn is_safe(&self) -> bool {
17        false
18    }
19}
20
21impl<F> Function for F
22where
23    F: Fn(&HashMap<String, Value>) -> Result<Value> + Sync + Send,
24{
25    fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
26        self(args)
27    }
28}
29
30pub fn range(args: &HashMap<String, Value>) -> Result<Value> {
31    let start = match args.get("start") {
32        Some(val) => match from_value::<usize>(val.clone()) {
33            Ok(v) => v,
34            Err(_) => {
35                return Err(Error::msg(format!(
36                    "Function `range` received start={} but `start` can only be a number",
37                    val
38                )));
39            }
40        },
41        None => 0,
42    };
43    let step_by = match args.get("step_by") {
44        Some(val) => match from_value::<usize>(val.clone()) {
45            Ok(v) => v,
46            Err(_) => {
47                return Err(Error::msg(format!(
48                    "Function `range` received step_by={} but `step` can only be a number",
49                    val
50                )));
51            }
52        },
53        None => 1,
54    };
55    let end = match args.get("end") {
56        Some(val) => match from_value::<usize>(val.clone()) {
57            Ok(v) => v,
58            Err(_) => {
59                return Err(Error::msg(format!(
60                    "Function `range` received end={} but `end` can only be a number",
61                    val
62                )));
63            }
64        },
65        None => {
66            return Err(Error::msg("Function `range` was called without a `end` argument"));
67        }
68    };
69
70    if start > end {
71        return Err(Error::msg(
72            "Function `range` was called with a `start` argument greater than the `end` one",
73        ));
74    }
75
76    let mut i = start;
77    let mut res = vec![];
78    while i < end {
79        res.push(i);
80        i += step_by;
81    }
82    Ok(to_value(res).unwrap())
83}
84
85pub fn now(args: &HashMap<String, Value>) -> Result<Value> {
86    let utc = match args.get("utc") {
87        Some(val) => match from_value::<bool>(val.clone()) {
88            Ok(v) => v,
89            Err(_) => {
90                return Err(Error::msg(format!(
91                    "Function `now` received utc={} but `utc` can only be a boolean",
92                    val
93                )));
94            }
95        },
96        None => false,
97    };
98    let timestamp = match args.get("timestamp") {
99        Some(val) => match from_value::<bool>(val.clone()) {
100            Ok(v) => v,
101            Err(_) => {
102                return Err(Error::msg(format!(
103                    "Function `now` received timestamp={} but `timestamp` can only be a boolean",
104                    val
105                )));
106            }
107        },
108        None => false,
109    };
110
111    if utc {
112        let datetime = Utc::now();
113        if timestamp {
114            return Ok(to_value(datetime.timestamp()).unwrap());
115        }
116        Ok(to_value(datetime.to_rfc3339()).unwrap())
117    } else {
118        let datetime = Local::now();
119        if timestamp {
120            return Ok(to_value(datetime.timestamp()).unwrap());
121        }
122        Ok(to_value(datetime.to_rfc3339()).unwrap())
123    }
124}
125
126pub fn throw(args: &HashMap<String, Value>) -> Result<Value> {
127    match args.get("message") {
128        Some(val) => match from_value::<String>(val.clone()) {
129            Ok(v) => Err(Error::msg(v)),
130            Err(_) => Err(Error::msg(format!(
131                "Function `throw` received message={} but `message` can only be a string",
132                val
133            ))),
134        },
135        None => Err(Error::msg("Function `throw` was called without a `message` argument")),
136    }
137}
138
139#[cfg(feature = "builtins")]
140pub fn get_random(args: &HashMap<String, Value>) -> Result<Value> {
141    let start = match args.get("start") {
142        Some(val) => match from_value::<i32>(val.clone()) {
143            Ok(v) => v,
144            Err(_) => {
145                return Err(Error::msg(format!(
146                    "Function `get_random` received start={} but `start` can only be a boolean",
147                    val
148                )));
149            }
150        },
151        None => 0,
152    };
153
154    let end = match args.get("end") {
155        Some(val) => match from_value::<i32>(val.clone()) {
156            Ok(v) => v,
157            Err(_) => {
158                return Err(Error::msg(format!(
159                    "Function `get_random` received end={} but `end` can only be a boolean",
160                    val
161                )));
162            }
163        },
164        None => return Err(Error::msg("Function `get_random` didn't receive an `end` argument")),
165    };
166    let mut rng = rand::thread_rng();
167    let res = rng.gen_range(start..end);
168
169    Ok(Value::Number(res.into()))
170}
171
172pub fn get_env(args: &HashMap<String, Value>) -> Result<Value> {
173    let name = match args.get("name") {
174        Some(val) => match from_value::<String>(val.clone()) {
175            Ok(v) => v,
176            Err(_) => {
177                return Err(Error::msg(format!(
178                    "Function `get_env` received name={} but `name` can only be a string",
179                    val
180                )));
181            }
182        },
183        None => return Err(Error::msg("Function `get_env` didn't receive a `name` argument")),
184    };
185
186    match std::env::var(&name).ok() {
187        Some(res) => Ok(Value::String(res)),
188        None => match args.get("default") {
189            Some(default) => Ok(default.clone()),
190            None => Err(Error::msg(format!("Environment variable `{}` not found", &name))),
191        },
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use std::collections::HashMap;
198
199    use serde_json::value::to_value;
200
201    use super::*;
202
203    #[test]
204    fn range_default() {
205        let mut args = HashMap::new();
206        args.insert("end".to_string(), to_value(5).unwrap());
207
208        let res = range(&args).unwrap();
209        assert_eq!(res, to_value(vec![0, 1, 2, 3, 4]).unwrap());
210    }
211
212    #[test]
213    fn range_start() {
214        let mut args = HashMap::new();
215        args.insert("end".to_string(), to_value(5).unwrap());
216        args.insert("start".to_string(), to_value(1).unwrap());
217
218        let res = range(&args).unwrap();
219        assert_eq!(res, to_value(vec![1, 2, 3, 4]).unwrap());
220    }
221
222    #[test]
223    fn range_start_greater_than_end() {
224        let mut args = HashMap::new();
225        args.insert("end".to_string(), to_value(5).unwrap());
226        args.insert("start".to_string(), to_value(6).unwrap());
227
228        assert!(range(&args).is_err());
229    }
230
231    #[test]
232    fn range_step_by() {
233        let mut args = HashMap::new();
234        args.insert("end".to_string(), to_value(10).unwrap());
235        args.insert("step_by".to_string(), to_value(2).unwrap());
236
237        let res = range(&args).unwrap();
238        assert_eq!(res, to_value(vec![0, 2, 4, 6, 8]).unwrap());
239    }
240
241    #[cfg(feature = "builtins")]
242    #[test]
243    fn now_default() {
244        let args = HashMap::new();
245
246        let res = now(&args).unwrap();
247        assert!(res.is_string());
248        assert!(res.as_str().unwrap().contains("T"));
249    }
250
251    #[cfg(feature = "builtins")]
252    #[test]
253    fn now_datetime_utc() {
254        let mut args = HashMap::new();
255        args.insert("utc".to_string(), to_value(true).unwrap());
256
257        let res = now(&args).unwrap();
258        assert!(res.is_string());
259        let val = res.as_str().unwrap();
260        println!("{}", val);
261        assert!(val.contains("T"));
262        assert!(val.contains("+00:00"));
263    }
264
265    #[cfg(feature = "builtins")]
266    #[test]
267    fn now_timestamp() {
268        let mut args = HashMap::new();
269        args.insert("timestamp".to_string(), to_value(true).unwrap());
270
271        let res = now(&args).unwrap();
272        assert!(res.is_number());
273    }
274
275    #[test]
276    fn throw_errors_with_message() {
277        let mut args = HashMap::new();
278        args.insert("message".to_string(), to_value("Hello").unwrap());
279
280        let res = throw(&args);
281        assert!(res.is_err());
282        let err = res.unwrap_err();
283        assert_eq!(err.to_string(), "Hello");
284    }
285
286    #[cfg(feature = "builtins")]
287    #[test]
288    fn get_random_no_start() {
289        let mut args = HashMap::new();
290        args.insert("end".to_string(), to_value(10).unwrap());
291        let res = get_random(&args).unwrap();
292        println!("{}", res);
293        assert!(res.is_number());
294        assert!(res.as_i64().unwrap() >= 0);
295        assert!(res.as_i64().unwrap() < 10);
296    }
297
298    #[cfg(feature = "builtins")]
299    #[test]
300    fn get_random_with_start() {
301        let mut args = HashMap::new();
302        args.insert("start".to_string(), to_value(5).unwrap());
303        args.insert("end".to_string(), to_value(10).unwrap());
304        let res = get_random(&args).unwrap();
305        println!("{}", res);
306        assert!(res.is_number());
307        assert!(res.as_i64().unwrap() >= 5);
308        assert!(res.as_i64().unwrap() < 10);
309    }
310
311    #[test]
312    fn get_env_existing() {
313        std::env::set_var("TERA_TEST", "true");
314        let mut args = HashMap::new();
315        args.insert("name".to_string(), to_value("TERA_TEST").unwrap());
316        let res = get_env(&args).unwrap();
317        assert!(res.is_string());
318        assert_eq!(res.as_str().unwrap(), "true");
319        std::env::remove_var("TERA_TEST");
320    }
321
322    #[test]
323    fn get_env_non_existing_no_default() {
324        let mut args = HashMap::new();
325        args.insert("name".to_string(), to_value("UNKNOWN_VAR").unwrap());
326        let res = get_env(&args);
327        assert!(res.is_err());
328    }
329
330    #[test]
331    fn get_env_non_existing_with_default() {
332        let mut args = HashMap::new();
333        args.insert("name".to_string(), to_value("UNKNOWN_VAR").unwrap());
334        args.insert("default".to_string(), to_value("false").unwrap());
335        let res = get_env(&args).unwrap();
336        assert!(res.is_string());
337        assert_eq!(res.as_str().unwrap(), "false");
338    }
339}