Skip to main content

bel/
lib.rs

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