cel_interpreter/magic.rs
1use crate::macros::{impl_conversions, impl_handler};
2use crate::resolvers::{AllArguments, Argument};
3use crate::{ExecutionError, Expression, FunctionContext, ResolveResult, Value};
4use cel_parser::ast::Expr;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8impl_conversions!(
9 i64 => Value::Int,
10 u64 => Value::UInt,
11 f64 => Value::Float,
12 Arc<String> => Value::String,
13 Arc<Vec<u8>> => Value::Bytes,
14 bool => Value::Bool,
15 Arc<Vec<Value>> => Value::List
16);
17
18#[cfg(feature = "chrono")]
19impl_conversions!(
20 chrono::Duration => Value::Duration,
21 chrono::DateTime<chrono::FixedOffset> => Value::Timestamp,
22);
23
24impl From<i32> for Value {
25 fn from(value: i32) -> Self {
26 Value::Int(value as i64)
27 }
28}
29
30/// Describes any type that can be converted from a [`Value`] into itself.
31/// This is commonly used to convert from [`Value`] into primitive types,
32/// e.g. from `Value::Bool(true) -> true`. This trait is auto-implemented
33/// for many CEL-primitive types.
34trait FromValue {
35 fn from_value(value: &Value) -> Result<Self, ExecutionError>
36 where
37 Self: Sized;
38}
39
40impl FromValue for Value {
41 fn from_value(value: &Value) -> Result<Self, ExecutionError>
42 where
43 Self: Sized,
44 {
45 Ok(value.clone())
46 }
47}
48
49/// A trait for types that can be converted into a [`ResolveResult`]. Every function that can
50/// be registered to the CEL context must return a value that implements this trait.
51pub trait IntoResolveResult {
52 fn into_resolve_result(self) -> ResolveResult;
53}
54
55impl IntoResolveResult for String {
56 fn into_resolve_result(self) -> ResolveResult {
57 Ok(Value::String(Arc::new(self)))
58 }
59}
60
61impl IntoResolveResult for Result<Value, ExecutionError> {
62 fn into_resolve_result(self) -> ResolveResult {
63 self
64 }
65}
66
67/// Describes any type that can be converted from a [`FunctionContext`] into
68/// itself, for example CEL primitives implement this trait to allow them to
69/// be used as arguments to functions. This trait is core to the 'magic function
70/// parameter' system. Every argument to a function that can be registered to
71/// the CEL context must implement this type.
72pub(crate) trait FromContext<'a, 'context> {
73 fn from_context(ctx: &'a mut FunctionContext<'context>) -> Result<Self, ExecutionError>
74 where
75 Self: Sized;
76}
77
78/// A function argument abstraction enabling dynamic method invocation on a
79/// target instance or on the first argument if the function is not called
80/// as a method.
81///
82/// This is similar to how methods can be called as functions using the
83/// [fully-qualified syntax](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#fully-qualified-syntax-for-disambiguation-calling-methods-with-the-same-name).
84///
85/// # Using `This`
86/// ```
87/// # use std::sync::Arc;
88/// # use cel_interpreter::{Program, Context};
89/// use cel_interpreter::extractors::This;
90/// # let mut context = Context::default();
91/// # context.add_function("startsWith", starts_with);
92///
93/// /// Notice how `This` refers to the target value when called as a method,
94/// /// but the first argument when called as a function.
95/// let program1 = "'foobar'.startsWith('foo') == true";
96/// let program2 = "startsWith('foobar', 'foo') == true";
97/// # let program1 = Program::compile(program1).unwrap();
98/// # let program2 = Program::compile(program2).unwrap();
99/// # let value = program1.execute(&context).unwrap();
100/// # assert_eq!(value, true.into());
101/// # let value = program2.execute(&context).unwrap();
102/// # assert_eq!(value, true.into());
103///
104/// fn starts_with(This(this): This<Arc<String>>, prefix: Arc<String>) -> bool {
105/// this.starts_with(prefix.as_str())
106/// }
107/// ```
108///
109/// # Type of `This`
110/// This also accepts a type `T` which determines the specific type
111/// that's extracted. Any type that supports [`FromValue`] can be used.
112/// In the previous example, the method `startsWith` is only ever called
113/// on a string, so we can use `This<Rc<String>>` to extract the string
114/// automatically prior to our method actually being called.
115///
116/// In some cases, you may want access to the raw [`Value`] instead, for
117/// example, the `contains` method works for several different types. In these
118/// cases, you can use `This<Value>` to extract the raw value.
119///
120/// ```skip
121/// pub fn contains(This(this): This<Value>, arg: Value) -> Result<Value> {
122/// Ok(match this {
123/// Value::List(v) => v.contains(&arg),
124/// ...
125/// }
126/// }
127/// ```
128pub struct This<T>(pub T);
129
130impl<'a, 'context, T> FromContext<'a, 'context> for This<T>
131where
132 T: FromValue,
133{
134 fn from_context(ctx: &'a mut FunctionContext<'context>) -> Result<Self, ExecutionError>
135 where
136 Self: Sized,
137 {
138 if let Some(ref this) = ctx.this {
139 Ok(This(T::from_value(this)?))
140 } else {
141 let arg = arg_value_from_context(ctx)
142 .map_err(|_| ExecutionError::missing_argument_or_target())?;
143 Ok(This(T::from_value(&arg)?))
144 }
145 }
146}
147
148/// Identifier is an argument extractor that attempts to extract an identifier
149/// from an argument's expression.
150///
151/// It fails if the argument is not available, or if the argument cannot be
152/// converted into an expression.
153///
154/// # Examples
155/// Identifiers are useful for functions like `.map` or `.filter` where one
156/// of the arguments is the declaration of a variable. In this case, as noted
157/// below, the x is an identifier, and we want to be able to parse it
158/// automatically.
159///
160/// ```javascript
161/// // Identifier
162/// // ↓
163/// [1, 2, 3].map(x, x * 2) == [2, 4, 6]
164/// ```
165///
166/// The function signature for the Rust implementation of `map` looks like this
167///
168/// ```skip
169/// pub fn map(
170/// ftx: &FunctionContext,
171/// This(this): This<Value>, // <- [1, 2, 3]
172/// ident: Identifier, // <- x
173/// expr: Expression, // <- x * 2
174/// ) -> Result<Value>;
175/// ```
176#[derive(Clone)]
177pub struct Identifier(pub Arc<String>);
178
179impl<'a, 'context> FromContext<'a, 'context> for Identifier {
180 fn from_context(ctx: &'a mut FunctionContext<'context>) -> Result<Self, ExecutionError>
181 where
182 Self: Sized,
183 {
184 match &arg_expr_from_context(ctx).expr {
185 Expr::Ident(ident) => Ok(Identifier(ident.clone().into())),
186 expr => Err(ExecutionError::UnexpectedType {
187 got: format!("{expr:?}"),
188 want: "identifier".to_string(),
189 }),
190 }
191 }
192}
193
194impl From<&Identifier> for String {
195 fn from(value: &Identifier) -> Self {
196 value.0.to_string()
197 }
198}
199
200impl From<Identifier> for String {
201 fn from(value: Identifier) -> Self {
202 value.0.as_ref().clone()
203 }
204}
205
206/// An argument extractor that extracts all the arguments passed to a function, resolves their
207/// expressions and returns a vector of [`Value`].
208///
209/// This is useful for functions that accept a variable number of arguments rather than known
210/// arguments and types (for example a `sum` function).
211///
212/// # Example
213/// ```javascript
214/// sum(1, 2.0, uint(3)) == 5.0
215/// ```
216///
217/// ```rust
218/// # use cel_interpreter::{Value};
219/// use cel_interpreter::extractors::Arguments;
220/// pub fn sum(Arguments(args): Arguments) -> Value {
221/// args.iter().fold(0.0, |acc, val| match val {
222/// Value::Int(x) => *x as f64 + acc,
223/// Value::UInt(x) => *x as f64 + acc,
224/// Value::Float(x) => *x + acc,
225/// _ => acc,
226/// }).into()
227/// }
228/// ```
229#[derive(Clone)]
230pub struct Arguments(pub Arc<Vec<Value>>);
231
232impl<'a> FromContext<'a, '_> for Arguments {
233 fn from_context(ctx: &'a mut FunctionContext) -> Result<Self, ExecutionError>
234 where
235 Self: Sized,
236 {
237 match ctx.resolve(AllArguments)? {
238 Value::List(list) => Ok(Arguments(list.clone())),
239 _ => todo!(),
240 }
241 }
242}
243
244impl<'a, 'context> FromContext<'a, 'context> for Value {
245 fn from_context(ctx: &'a mut FunctionContext<'context>) -> Result<Self, ExecutionError>
246 where
247 Self: Sized,
248 {
249 arg_value_from_context(ctx)
250 }
251}
252
253impl<'a, 'context> FromContext<'a, 'context> for Expression {
254 fn from_context(ctx: &'a mut FunctionContext<'context>) -> Result<Self, ExecutionError>
255 where
256 Self: Sized,
257 {
258 Ok(arg_expr_from_context(ctx).clone())
259 }
260}
261
262/// Returns the next argument specified by the context's `arg_idx` field as an expression
263/// (i.e. not resolved). Calling this multiple times will increment the `arg_idx` which will
264/// return subsequent arguments every time.
265///
266/// Calling this function when there are no more arguments will result in a panic. Since this
267/// function is only ever called within the context of a controlled macro that calls it once
268/// for each argument, this should never happen.
269fn arg_expr_from_context<'a>(ctx: &'a mut FunctionContext) -> &'a Expression {
270 let idx = ctx.arg_idx;
271 ctx.arg_idx += 1;
272 &ctx.args[idx]
273}
274
275/// Returns the next argument specified by the context's `arg_idx` field as after resolving
276/// it. Calling this multiple times will increment the `arg_idx` which will return subsequent
277/// arguments every time.
278///
279/// Calling this function when there are no more arguments will result in a panic. Since this
280/// function is only ever called within the context of a controlled macro that calls it once
281/// for each argument, this should never happen.
282fn arg_value_from_context(ctx: &mut FunctionContext) -> Result<Value, ExecutionError> {
283 let idx = ctx.arg_idx;
284 ctx.arg_idx += 1;
285 ctx.resolve(Argument(idx))
286}
287
288pub struct WithFunctionContext;
289
290impl_handler!();
291impl_handler!(C1);
292impl_handler!(C1, C2);
293impl_handler!(C1, C2, C3);
294impl_handler!(C1, C2, C3, C4);
295impl_handler!(C1, C2, C3, C4, C5);
296impl_handler!(C1, C2, C3, C4, C5, C6);
297impl_handler!(C1, C2, C3, C4, C5, C6, C7);
298impl_handler!(C1, C2, C3, C4, C5, C6, C7, C8);
299impl_handler!(C1, C2, C3, C4, C5, C6, C7, C8, C9);
300
301// Heavily inspired by https://users.rust-lang.org/t/common-data-type-for-functions-with-different-parameters-e-g-axum-route-handlers/90207/6
302// and https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c6744c27c2358ec1d1196033a0ec11e4
303
304#[derive(Default)]
305pub struct FunctionRegistry {
306 functions: HashMap<String, Function>,
307}
308
309impl FunctionRegistry {
310 pub(crate) fn add<F, T>(&mut self, name: &str, function: F)
311 where
312 F: IntoFunction<T> + 'static + Send + Sync,
313 T: 'static,
314 {
315 self.functions
316 .insert(name.to_string(), function.into_function());
317 }
318
319 pub(crate) fn get(&self, name: &str) -> Option<&Function> {
320 self.functions.get(name)
321 }
322
323 pub(crate) fn has(&self, name: &str) -> bool {
324 self.functions.contains_key(name)
325 }
326}
327
328pub type Function = Box<dyn Fn(&mut FunctionContext) -> ResolveResult + Send + Sync>;
329
330pub trait IntoFunction<T> {
331 fn into_function(self) -> Function;
332}