cel_interpreter/
context.rs

1use crate::magic::{Function, FunctionRegistry, IntoFunction};
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: AsRef<str>,
82    {
83        let name = name.as_ref();
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.to_string().into())),
90            Context::Root { variables, .. } => variables
91                .get(name)
92                .cloned()
93                .ok_or_else(|| ExecutionError::UndeclaredReference(name.to_string().into())),
94        }
95    }
96
97    pub(crate) fn has_function(&self, name: &str) -> bool {
98        match self {
99            Context::Root { functions, .. } => functions.has(name),
100            Context::Child { parent, .. } => parent.has_function(name),
101        }
102    }
103
104    pub(crate) fn get_function(&self, name: &str) -> Option<&Function> {
105        match self {
106            Context::Root { functions, .. } => functions.get(name),
107            Context::Child { parent, .. } => parent.get_function(name),
108        }
109    }
110
111    pub fn add_function<T: 'static, F>(&mut self, name: &str, value: F)
112    where
113        F: IntoFunction<T> + 'static + Send + Sync,
114    {
115        if let Context::Root { functions, .. } = self {
116            functions.add(name, value);
117        };
118    }
119
120    pub fn resolve(&self, expr: &Expression) -> Result<Value, ExecutionError> {
121        Value::resolve(expr, self)
122    }
123
124    pub fn resolve_all(&self, exprs: &[Expression]) -> Result<Value, ExecutionError> {
125        Value::resolve_all(exprs, self)
126    }
127
128    pub fn new_inner_scope(&self) -> Context {
129        Context::Child {
130            parent: self,
131            variables: Default::default(),
132        }
133    }
134
135    /// Constructs a new empty context with no variables or functions.
136    ///
137    /// If you're looking for a context that has all the standard methods, functions
138    /// and macros already added to the context, use [`Context::default`] instead.
139    ///
140    /// # Example
141    /// ```
142    /// use cel_interpreter::Context;
143    /// let mut context = Context::empty();
144    /// context.add_function("add", |a: i64, b: i64| a + b);
145    /// ```
146    pub fn empty() -> Self {
147        Context::Root {
148            variables: Default::default(),
149            functions: Default::default(),
150        }
151    }
152}
153
154impl Default for Context<'_> {
155    fn default() -> Self {
156        let mut ctx = Context::Root {
157            variables: Default::default(),
158            functions: Default::default(),
159        };
160
161        ctx.add_function("contains", functions::contains);
162        ctx.add_function("size", functions::size);
163        ctx.add_function("max", functions::max);
164        ctx.add_function("min", functions::min);
165        ctx.add_function("startsWith", functions::starts_with);
166        ctx.add_function("endsWith", functions::ends_with);
167        ctx.add_function("string", functions::string);
168        ctx.add_function("bytes", functions::bytes);
169        ctx.add_function("double", functions::double);
170        ctx.add_function("int", functions::int);
171        ctx.add_function("uint", functions::uint);
172
173        #[cfg(feature = "regex")]
174        ctx.add_function("matches", functions::matches);
175
176        #[cfg(feature = "chrono")]
177        {
178            ctx.add_function("duration", functions::duration);
179            ctx.add_function("timestamp", functions::timestamp);
180            ctx.add_function("getFullYear", functions::time::timestamp_year);
181            ctx.add_function("getMonth", functions::time::timestamp_month);
182            ctx.add_function("getDayOfYear", functions::time::timestamp_year_day);
183            ctx.add_function("getDayOfMonth", functions::time::timestamp_month_day);
184            ctx.add_function("getDate", functions::time::timestamp_date);
185            ctx.add_function("getDayOfWeek", functions::time::timestamp_weekday);
186            ctx.add_function("getHours", functions::time::timestamp_hours);
187            ctx.add_function("getMinutes", functions::time::timestamp_minutes);
188            ctx.add_function("getSeconds", functions::time::timestamp_seconds);
189            ctx.add_function("getMilliseconds", functions::time::timestamp_millis);
190        }
191
192        ctx
193    }
194}