tera_v1/builtins/
functions.rs1use std::collections::HashMap;
2
3use chrono::prelude::*;
4use serde_json::value::{from_value, to_value, Value};
5
6use crate::errors::{Error, Result};
7
8pub trait Function: Sync + Send {
10 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}