cel_interpreter/
lib.rs

1extern crate core;
2
3use std::convert::TryFrom;
4use std::sync::Arc;
5use thiserror::Error;
6
7mod macros;
8
9pub mod context;
10pub use cel_parser::ast::IdedExpr;
11use cel_parser::ast::SelectExpr;
12use cel_parser::{Expression, ExpressionReferences, Parser};
13pub use cel_parser::{ParseError, ParseErrors};
14pub use context::Context;
15pub use functions::FunctionContext;
16pub use objects::{ResolveResult, Value};
17pub mod functions;
18mod magic;
19pub mod objects;
20mod resolvers;
21
22#[cfg(feature = "chrono")]
23mod duration;
24#[cfg(feature = "chrono")]
25pub use ser::{Duration, Timestamp};
26
27mod ser;
28pub use ser::to_value;
29pub use ser::SerializationError;
30
31#[cfg(feature = "json")]
32mod json;
33#[cfg(feature = "json")]
34pub use json::ConvertToJsonError;
35
36use magic::FromContext;
37
38pub mod extractors {
39    pub use crate::magic::{Arguments, Identifier, This};
40}
41
42#[derive(Error, Clone, Debug, PartialEq)]
43#[non_exhaustive]
44pub enum ExecutionError {
45    #[error("Invalid argument count: expected {expected}, got {actual}")]
46    InvalidArgumentCount { expected: usize, actual: usize },
47    #[error("Invalid argument type: {:?}", .target)]
48    UnsupportedTargetType { target: Value },
49    #[error("Method '{method}' not supported on type '{target:?}'")]
50    NotSupportedAsMethod { method: String, target: Value },
51    /// Indicates that the script attempted to use a value as a key in a map,
52    /// but the type of the value was not supported as a key.
53    #[error("Unable to use value '{0:?}' as a key")]
54    UnsupportedKeyType(Value),
55    #[error("Unexpected type: got '{got}', want '{want}'")]
56    UnexpectedType { got: String, want: String },
57    /// Indicates that the script attempted to reference a key on a type that
58    /// was missing the requested key.
59    #[error("No such key: {0}")]
60    NoSuchKey(Arc<String>),
61    /// Indicates that the script attempted to reference an undeclared variable
62    /// method, or function.
63    #[error("Undeclared reference to '{0}'")]
64    UndeclaredReference(Arc<String>),
65    /// Indicates that a function expected to be called as a method, or to be
66    /// called with at least one parameter.
67    #[error("Missing argument or target")]
68    MissingArgumentOrTarget,
69    /// Indicates that a comparison could not be performed.
70    #[error("{0:?} can not be compared to {1:?}")]
71    ValuesNotComparable(Value, Value),
72    /// Indicates that an operator was used on a type that does not support it.
73    #[error("Unsupported unary operator '{0}': {1:?}")]
74    UnsupportedUnaryOperator(&'static str, Value),
75    /// Indicates that an unsupported binary operator was applied on two values
76    /// where it's unsupported, for example list + map.
77    #[error("Unsupported binary operator '{0}': {1:?}, {2:?}")]
78    UnsupportedBinaryOperator(&'static str, Value, Value),
79    /// Indicates that an unsupported type was used to index a map
80    #[error("Cannot use value as map index: {0:?}")]
81    UnsupportedMapIndex(Value),
82    /// Indicates that an unsupported type was used to index a list
83    #[error("Cannot use value as list index: {0:?}")]
84    UnsupportedListIndex(Value),
85    /// Indicates that an unsupported type was used to index a list
86    #[error("Cannot use value {0:?} to index {1:?}")]
87    UnsupportedIndex(Value, Value),
88    /// Indicates that a function call occurred without an [`Expression::Ident`]
89    /// as the function identifier.
90    #[error("Unsupported function call identifier type: {0:?}")]
91    UnsupportedFunctionCallIdentifierType(Expression),
92    /// Indicates that a [`Member::Fields`] construction was attempted
93    /// which is not yet supported.
94    #[error("Unsupported fields construction: {0:?}")]
95    UnsupportedFieldsConstruction(SelectExpr),
96    /// Indicates that a function had an error during execution.
97    #[error("Error executing function '{function}': {message}")]
98    FunctionError { function: String, message: String },
99    #[error("Division by zero of {0:?}")]
100    DivisionByZero(Value),
101    #[error("Remainder by zero of {0:?}")]
102    RemainderByZero(Value),
103    #[error("Overflow from binary operator '{0}': {1:?}, {2:?}")]
104    Overflow(&'static str, Value, Value),
105}
106
107impl ExecutionError {
108    pub fn no_such_key(name: &str) -> Self {
109        ExecutionError::NoSuchKey(Arc::new(name.to_string()))
110    }
111
112    pub fn undeclared_reference(name: &str) -> Self {
113        ExecutionError::UndeclaredReference(Arc::new(name.to_string()))
114    }
115
116    pub fn invalid_argument_count(expected: usize, actual: usize) -> Self {
117        ExecutionError::InvalidArgumentCount { expected, actual }
118    }
119
120    pub fn function_error<E: ToString>(function: &str, error: E) -> Self {
121        ExecutionError::FunctionError {
122            function: function.to_string(),
123            message: error.to_string(),
124        }
125    }
126
127    pub fn unsupported_target_type(target: Value) -> Self {
128        ExecutionError::UnsupportedTargetType { target }
129    }
130
131    pub fn not_supported_as_method(method: &str, target: Value) -> Self {
132        ExecutionError::NotSupportedAsMethod {
133            method: method.to_string(),
134            target,
135        }
136    }
137
138    pub fn unsupported_key_type(value: Value) -> Self {
139        ExecutionError::UnsupportedKeyType(value)
140    }
141
142    pub fn missing_argument_or_target() -> Self {
143        ExecutionError::MissingArgumentOrTarget
144    }
145}
146
147#[derive(Debug)]
148pub struct Program {
149    expression: Expression,
150}
151
152impl Program {
153    pub fn compile(source: &str) -> Result<Program, ParseErrors> {
154        let parser = Parser::default();
155        parser
156            .parse(source)
157            .map(|expression| Program { expression })
158    }
159
160    pub fn execute(&self, context: &Context) -> ResolveResult {
161        Value::resolve(&self.expression, context)
162    }
163
164    /// Returns the variables and functions referenced by the CEL program
165    ///
166    /// # Example
167    /// ```rust
168    /// # use cel_interpreter::Program;
169    /// let program = Program::compile("size(foo) > 0").unwrap();
170    /// let references = program.references();
171    ///
172    /// assert!(references.has_function("size"));
173    /// assert!(references.has_variable("foo"));
174    /// ```
175    pub fn references(&self) -> ExpressionReferences {
176        self.expression.references()
177    }
178}
179
180impl TryFrom<&str> for Program {
181    type Error = ParseErrors;
182
183    fn try_from(value: &str) -> Result<Self, Self::Error> {
184        Program::compile(value)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use crate::context::Context;
191    use crate::objects::{ResolveResult, Value};
192    use crate::{ExecutionError, Program};
193    use std::collections::HashMap;
194    use std::convert::TryInto;
195
196    /// Tests the provided script and returns the result. An optional context can be provided.
197    pub(crate) fn test_script(script: &str, ctx: Option<Context>) -> ResolveResult {
198        let program = match Program::compile(script) {
199            Ok(p) => p,
200            Err(e) => panic!("{}", e),
201        };
202        program.execute(&ctx.unwrap_or_default())
203    }
204
205    #[test]
206    fn parse() {
207        Program::compile("1 + 1").unwrap();
208    }
209
210    #[test]
211    fn from_str() {
212        let input = "1.1";
213        let _p: Program = input.try_into().unwrap();
214    }
215
216    #[test]
217    fn variables() {
218        fn assert_output(script: &str, expected: ResolveResult) {
219            let mut ctx = Context::default();
220            ctx.add_variable_from_value("foo", HashMap::from([("bar", 1i64)]));
221            ctx.add_variable_from_value("arr", vec![1i64, 2, 3]);
222            ctx.add_variable_from_value("str", "foobar".to_string());
223            assert_eq!(test_script(script, Some(ctx)), expected);
224        }
225
226        // Test methods
227        assert_output("size([1, 2, 3]) == 3", Ok(true.into()));
228        assert_output("size([]) == 3", Ok(false.into()));
229
230        // Test variable attribute traversals
231        assert_output("foo.bar == 1", Ok(true.into()));
232
233        // Test that we can index into an array
234        assert_output("arr[0] == 1", Ok(true.into()));
235
236        // Test that we can index into a string
237        assert_output("str[0] == 'f'", Ok(true.into()));
238    }
239
240    #[test]
241    fn references() {
242        let p = Program::compile("[1, 1].map(x, x * 2)").unwrap();
243        assert!(p.references().has_variable("x"));
244        assert_eq!(p.references().variables().len(), 1);
245    }
246
247    #[test]
248    fn test_execution_errors() {
249        let tests = vec![
250            (
251                "no such key",
252                "foo.baz.bar == 1",
253                ExecutionError::no_such_key("baz"),
254            ),
255            (
256                "undeclared reference",
257                "missing == 1",
258                ExecutionError::undeclared_reference("missing"),
259            ),
260            (
261                "undeclared method",
262                "1.missing()",
263                ExecutionError::undeclared_reference("missing"),
264            ),
265            (
266                "undeclared function",
267                "missing(1)",
268                ExecutionError::undeclared_reference("missing"),
269            ),
270            (
271                "unsupported key type",
272                "{null: true}",
273                ExecutionError::unsupported_key_type(Value::Null),
274            ),
275        ];
276
277        for (name, script, error) in tests {
278            let mut ctx = Context::default();
279            ctx.add_variable_from_value("foo", HashMap::from([("bar", 1)]));
280            let res = test_script(script, Some(ctx));
281            assert_eq!(res, error.into(), "{name}");
282        }
283    }
284}