Skip to main content

bel/
context.rs

1use std::collections::HashMap;
2
3use crate::{
4    ExecutionError, functions,
5    magic::{Function, FunctionRegistry, IntoFunction},
6    objects::{TryIntoValue, Value},
7    parser::Expression,
8};
9
10/// Context is a collection of variables and functions that can be used
11/// by the interpreter to resolve expressions.
12///
13/// The context can be either a parent context, or a child context. A
14/// parent context is created by default and contains all of the built-in
15/// functions. A child context can be created by calling `.clone()`. The
16/// child context has it's own variables (which can be added to), but it
17/// will also reference the parent context. This allows for variables to
18/// be overridden within the child context while still being able to
19/// resolve variables in the child's parents. You can have theoretically
20/// have an infinite number of child contexts that reference each-other.
21///
22/// So why is this important? Well some CEL-macros such as the `.map` macro
23/// declare intermediate user-specified identifiers that should only be
24/// available within the macro, and should not override variables in the
25/// parent context. The `.map` macro can clone the parent context, add the
26/// intermediate identifier to the child context, and then evaluate the
27/// map expression.
28///
29/// Intermediate variable stored in child context
30///               ↓
31/// [1, 2, 3].map(x, x * 2) == [2, 4, 6]
32///                  ↑
33/// Only in scope for the duration of the map expression
34///
35pub enum Context<'a> {
36    Root {
37        functions: FunctionRegistry,
38        variables: HashMap<String, Value>,
39    },
40    Child {
41        parent: &'a Context<'a>,
42        variables: HashMap<String, Value>,
43    },
44}
45
46impl Context<'_> {
47    pub fn add_variable<S, V>(&mut self, name: S, value: V) -> Result<(), <V as TryIntoValue>::Error>
48    where
49        S: Into<String>,
50        V: TryIntoValue,
51    {
52        match self {
53            Context::Root {
54                variables, ..
55            } => {
56                variables.insert(name.into(), value.try_into_value()?);
57            }
58            Context::Child {
59                variables, ..
60            } => {
61                variables.insert(name.into(), value.try_into_value()?);
62            }
63        }
64        Ok(())
65    }
66
67    pub fn add_variable_from_value<S, V>(&mut self, name: S, value: V)
68    where
69        S: Into<String>,
70        V: Into<Value>,
71    {
72        match self {
73            Context::Root {
74                variables, ..
75            } => {
76                variables.insert(name.into(), value.into());
77            }
78            Context::Child {
79                variables, ..
80            } => {
81                variables.insert(name.into(), value.into());
82            }
83        }
84    }
85
86    pub fn get_variable<S>(&self, name: S) -> Result<Value, ExecutionError>
87    where
88        S: AsRef<str>,
89    {
90        let name = name.as_ref();
91        match self {
92            Context::Child {
93                variables,
94                parent,
95            } => variables
96                .get(name)
97                .cloned()
98                .or_else(|| parent.get_variable(name).ok())
99                .ok_or_else(|| ExecutionError::UndeclaredReference(name.to_string().into())),
100            Context::Root {
101                variables, ..
102            } => variables
103                .get(name)
104                .cloned()
105                .ok_or_else(|| ExecutionError::UndeclaredReference(name.to_string().into())),
106        }
107    }
108
109    pub(crate) fn get_function(&self, name: &str) -> Option<&Function> {
110        match self {
111            Context::Root {
112                functions, ..
113            } => functions.get(name),
114            Context::Child {
115                parent, ..
116            } => parent.get_function(name),
117        }
118    }
119
120    pub fn add_function<T: 'static, F>(&mut self, name: &str, value: F)
121    where
122        F: IntoFunction<T> + 'static + Send + Sync,
123    {
124        if let Context::Root {
125            functions, ..
126        } = self
127        {
128            functions.add(name, value);
129        };
130    }
131
132    pub fn resolve(&self, expr: &Expression) -> Result<Value, ExecutionError> {
133        Value::resolve(expr, self)
134    }
135
136    pub fn resolve_all(&self, exprs: &[Expression]) -> Result<Value, ExecutionError> {
137        Value::resolve_all(exprs, self)
138    }
139
140    pub fn new_inner_scope(&self) -> Context<'_> {
141        Context::Child {
142            parent: self,
143            variables: Default::default(),
144        }
145    }
146
147    /// Constructs a new empty context with no variables or functions.
148    ///
149    /// If you're looking for a context that has all the standard methods, functions
150    /// and macros already added to the context, use [`Context::default`] instead.
151    ///
152    /// # Example
153    /// ```
154    /// use bel::Context;
155    /// let mut context = Context::empty();
156    /// context.add_function("add", |a: i64, b: i64| a + b);
157    /// ```
158    pub fn empty() -> Self {
159        Context::Root {
160            variables: Default::default(),
161            functions: Default::default(),
162        }
163    }
164}
165
166impl Default for Context<'_> {
167    fn default() -> Self {
168        let mut ctx = Context::Root {
169            variables: Default::default(),
170            functions: Default::default(),
171        };
172
173        ctx.add_function("contains", functions::contains);
174        ctx.add_function("length", functions::length);
175        ctx.add_function("max", functions::max);
176        ctx.add_function("min", functions::min);
177        ctx.add_function("starts_with", functions::starts_with);
178        ctx.add_function("ends_with", functions::ends_with);
179
180        ctx.add_function("String", functions::string);
181        ctx.add_function("Bytes", functions::bytes);
182        ctx.add_function("Float", functions::float);
183        ctx.add_function("Int", functions::int);
184        // ctx.add_function("Uint", functions::uint);
185
186        #[cfg(feature = "regex")]
187        {
188            ctx.add_function("matches", functions::matches);
189            ctx.add_function("Regex", functions::regex);
190        }
191
192        #[cfg(feature = "time")]
193        {
194            ctx.add_function("Duration", functions::duration);
195            ctx.add_function("Timestamp", functions::timestamp);
196
197            ctx.add_function("year", functions::time::timestamp_year);
198            ctx.add_function("month", functions::time::timestamp_month);
199            ctx.add_function("seconds", functions::time::timestamp_seconds);
200            ctx.add_function("milliseconds", functions::time::timestamp_millis);
201            ctx.add_function("unix", functions::time::unix);
202            ctx.add_function("now", functions::time::now);
203
204            ctx.add_function("getDayOfYear", functions::time::timestamp_year_day);
205            ctx.add_function("getDayOfMonth", functions::time::timestamp_month_day);
206            ctx.add_function("getDate", functions::time::timestamp_date);
207            ctx.add_function("getDayOfWeek", functions::time::timestamp_weekday);
208            ctx.add_function("getHours", functions::time::timestamp_hours);
209            ctx.add_function("getMinutes", functions::time::timestamp_minutes);
210        }
211
212        #[cfg(feature = "ip")]
213        {
214            ctx.add_function("Ip", functions::ip);
215        }
216
217        ctx
218    }
219}