Skip to main content

bel/
magic.rs

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