archetect_core/vendor/tera/builtins/
functions.rs1use 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
10pub trait Function: Sync + Send {
12 fn call(&self, args: &HashMap<String, Value>) -> Result<Value>;
14
15 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}