cel_interpreter/
context.rs

1use crate::magic::{Function, FunctionRegistry, Handler};
2use crate::objects::{TryIntoValue, Value};
3use crate::{functions, ExecutionError};
4use cel_parser::Expression;
5use std::collections::HashMap;
6
7/// Context is a collection of variables and functions that can be used
8/// by the interpreter to resolve expressions.
9///
10/// The context can be either a parent context, or a child context. A
11/// parent context is created by default and contains all of the built-in
12/// functions. A child context can be created by calling `.clone()`. The
13/// child context has it's own variables (which can be added to), but it
14/// will also reference the parent context. This allows for variables to
15/// be overridden within the child context while still being able to
16/// resolve variables in the child's parents. You can have theoretically
17/// have an infinite number of child contexts that reference each-other.
18///
19/// So why is this important? Well some CEL-macros such as the `.map` macro
20/// declare intermediate user-specified identifiers that should only be
21/// available within the macro, and should not override variables in the
22/// parent context. The `.map` macro can clone the parent context, add the
23/// intermediate identifier to the child context, and then evaluate the
24/// map expression.
25///
26/// Intermediate variable stored in child context
27///               ↓
28/// [1, 2, 3].map(x, x * 2) == [2, 4, 6]
29///                  ↑
30/// Only in scope for the duration of the map expression
31///
32pub 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    /// Constructs a new empty context with no variables or functions.
144    ///
145    /// If you're looking for a context that has all the standard methods, functions
146    /// and macros already added to the context, use [`Context::default`] instead.
147    ///
148    /// # Example
149    /// ```
150    /// use cel_interpreter::Context;
151    /// let mut context = Context::empty();
152    /// context.add_function("add", |a: i64, b: i64| a + b);
153    /// ```
154    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}