1use crate::magic::{Function, FunctionRegistry, Handler};
2use crate::objects::{TryIntoValue, Value};
3use crate::{functions, ExecutionError};
4use cel_parser::Expression;
5use std::collections::HashMap;
6
7pub enum Context<'a> {
33 Root {
34 functions: FunctionRegistry,
35 variables: HashMap<String, Value>,
36 },
37 Child {
38 parent: &'a Context<'a>,
39 variables: HashMap<String, Value>,
40 },
41}
42
43impl Context<'_> {
44 pub fn add_variable<S, V>(
45 &mut self,
46 name: S,
47 value: V,
48 ) -> Result<(), <V as TryIntoValue>::Error>
49 where
50 S: Into<String>,
51 V: TryIntoValue,
52 {
53 match self {
54 Context::Root { variables, .. } => {
55 variables.insert(name.into(), value.try_into_value()?);
56 }
57 Context::Child { variables, .. } => {
58 variables.insert(name.into(), value.try_into_value()?);
59 }
60 }
61 Ok(())
62 }
63
64 pub fn add_variable_from_value<S, V>(&mut self, name: S, value: V)
65 where
66 S: Into<String>,
67 V: Into<Value>,
68 {
69 match self {
70 Context::Root { variables, .. } => {
71 variables.insert(name.into(), value.into());
72 }
73 Context::Child { variables, .. } => {
74 variables.insert(name.into(), value.into());
75 }
76 }
77 }
78
79 pub fn get_variable<S>(&self, name: S) -> Result<Value, ExecutionError>
80 where
81 S: Into<String>,
82 {
83 let name = name.into();
84 match self {
85 Context::Child { variables, parent } => variables
86 .get(&name)
87 .cloned()
88 .or_else(|| parent.get_variable(&name).ok())
89 .ok_or_else(|| ExecutionError::UndeclaredReference(name.into())),
90 Context::Root { variables, .. } => variables
91 .get(&name)
92 .cloned()
93 .ok_or_else(|| ExecutionError::UndeclaredReference(name.into())),
94 }
95 }
96
97 pub(crate) fn has_function<S>(&self, name: S) -> bool
98 where
99 S: Into<String>,
100 {
101 let name = name.into();
102 match self {
103 Context::Root { functions, .. } => functions.has(&name),
104 Context::Child { parent, .. } => parent.has_function(name),
105 }
106 }
107
108 pub(crate) fn get_function<S>(&self, name: S) -> Option<Box<dyn Function>>
109 where
110 S: Into<String>,
111 {
112 let name = name.into();
113 match self {
114 Context::Root { functions, .. } => functions.get(&name),
115 Context::Child { parent, .. } => parent.get_function(name),
116 }
117 }
118
119 pub fn add_function<T: 'static, F>(&mut self, name: &str, value: F)
120 where
121 F: Handler<T> + 'static + Send + Sync,
122 {
123 if let Context::Root { functions, .. } = self {
124 functions.add(name, value);
125 };
126 }
127
128 pub fn resolve(&self, expr: &Expression) -> Result<Value, ExecutionError> {
129 Value::resolve(expr, self)
130 }
131
132 pub fn resolve_all(&self, exprs: &[Expression]) -> Result<Value, ExecutionError> {
133 Value::resolve_all(exprs, self)
134 }
135
136 pub fn new_inner_scope(&self) -> Context {
137 Context::Child {
138 parent: self,
139 variables: Default::default(),
140 }
141 }
142
143 pub fn empty() -> Self {
155 Context::Root {
156 variables: Default::default(),
157 functions: Default::default(),
158 }
159 }
160}
161
162impl Default for Context<'_> {
163 fn default() -> Self {
164 let mut ctx = Context::Root {
165 variables: Default::default(),
166 functions: Default::default(),
167 };
168
169 ctx.add_function("contains", functions::contains);
170 ctx.add_function("size", functions::size);
171 ctx.add_function("has", functions::has);
172 ctx.add_function("map", functions::map);
173 ctx.add_function("filter", functions::filter);
174 ctx.add_function("all", functions::all);
175 ctx.add_function("max", functions::max);
176 ctx.add_function("min", functions::min);
177 ctx.add_function("startsWith", functions::starts_with);
178 ctx.add_function("endsWith", functions::ends_with);
179 ctx.add_function("string", functions::string);
180 ctx.add_function("bytes", functions::bytes);
181 ctx.add_function("double", functions::double);
182 ctx.add_function("exists", functions::exists);
183 ctx.add_function("exists_one", functions::exists_one);
184 ctx.add_function("int", functions::int);
185 ctx.add_function("uint", functions::uint);
186
187 #[cfg(feature = "regex")]
188 ctx.add_function("matches", functions::matches);
189
190 #[cfg(feature = "chrono")]
191 {
192 ctx.add_function("duration", functions::duration);
193 ctx.add_function("timestamp", functions::timestamp);
194 ctx.add_function("getFullYear", functions::time::timestamp_year);
195 ctx.add_function("getMonth", functions::time::timestamp_month);
196 ctx.add_function("getDayOfYear", functions::time::timestamp_year_day);
197 ctx.add_function("getDayOfMonth", functions::time::timestamp_month_day);
198 ctx.add_function("getDate", functions::time::timestamp_date);
199 ctx.add_function("getDayOfWeek", functions::time::timestamp_weekday);
200 ctx.add_function("getHours", functions::time::timestamp_hours);
201 ctx.add_function("getMinutes", functions::time::timestamp_minutes);
202 ctx.add_function("getSeconds", functions::time::timestamp_seconds);
203 ctx.add_function("getMilliseconds", functions::time::timestamp_millis);
204 }
205
206 ctx
207 }
208}