cel_interpreter/
lib.rs

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