cel_cxx/
activation.rs

1//! Activation context for expression evaluation.
2//!
3//! This module provides the [`Activation`] type, which serves as the runtime context
4//! for CEL expression evaluation. Activations contain variable bindings and function
5//! implementations that are used during expression evaluation.
6//!
7//! # Key Concepts
8//!
9//! ## Activation Interface
10//!
11//! The [`ActivationInterface`] trait defines the contract for providing variable
12//! and function bindings to the CEL evaluator. It provides access to:
13//!
14//! - **Variable bindings**: Map variable names to runtime values
15//! - **Function bindings**: Provide runtime function implementations
16//!
17//! ## Activation Types
18//!
19//! The module provides several activation types:
20//!
21//! - **`Activation<'f>`**: Standard activation for synchronous evaluation
22//! - **`AsyncActivation<'f>`**: Activation with async function support
23//! - **`()`**: Empty activation for expressions without variables/functions
24//!
25//! # Variable Binding
26//!
27//! Variables can be bound to values that match the types declared in the environment:
28//!
29//! ```rust,no_run
30//! use cel_cxx::*;
31//!
32//! let activation = Activation::new()
33//!     .bind_variable("user_name", "Alice".to_string())?
34//!     .bind_variable("user_age", 30i64)?
35//!     .bind_variable("is_admin", true)?;
36//! # Ok::<(), cel_cxx::Error>(())
37//! ```
38//!
39//! # Function Binding
40//!
41//! Functions can be bound at runtime to override or supplement environment functions:
42//!
43//! ```rust,no_run
44//! use cel_cxx::*;
45//!
46//! let activation = Activation::new()
47//!     .bind_global_function("custom_add", |a: i64, b: i64| a + b)?
48//!     .bind_member_function("to_upper", |s: String| s.to_uppercase())?;
49//! # Ok::<(), cel_cxx::Error>(())
50//! ```
51//!
52//! # Variable Providers
53//!
54//! For dynamic or computed values, you can bind variable providers:
55//!
56//! ```rust,no_run
57//! use cel_cxx::*;
58//!
59//! let activation = Activation::new()
60//!     .bind_variable_provider("current_time", || {
61//!         std::time::SystemTime::now()
62//!             .duration_since(std::time::UNIX_EPOCH)
63//!             .unwrap()
64//!             .as_secs() as i64
65//!     })?;
66//! # Ok::<(), cel_cxx::Error>(())
67//! ```
68//!
69//! # Empty Activations
70//!
71//! For expressions that don't require any bindings, you can use the unit type:
72//!
73//! ```rust,no_run
74//! use cel_cxx::*;
75//!
76//! let env = Env::builder().build()?;
77//! let program = env.compile("1 + 2 * 3")?;
78//! let result = program.evaluate(())?; // No activation needed
79//! # Ok::<(), cel_cxx::Error>(())
80//! ```
81//!
82//! # Async Support
83//!
84//! When the `async` feature is enabled, activations can contain async functions:
85//!
86//! ```rust,no_run
87//! # #[cfg(feature = "async")]
88//! # async fn example() -> Result<(), cel_cxx::Error> {
89//! use cel_cxx::*;
90//!
91//! let activation = AsyncActivation::new_async()
92//!     .bind_global_function("fetch_data", |url: String| async move {
93//!         // Simulate async work
94//!         format!("Data from {}", url)
95//!     })?;
96//! # Ok(())
97//! # }
98//! ```
99
100use crate::function::FunctionBindings;
101use crate::function::{Arguments, IntoFunction};
102use crate::marker::*;
103use crate::values::{IntoValue, TypedValue};
104use crate::variable::VariableBindings;
105use crate::Error;
106use std::marker::PhantomData;
107
108/// Interface for providing variable and function bindings during evaluation.
109///
110/// This trait defines the interface that activation types must implement
111/// to provide variable and function bindings to the CEL evaluator.
112///
113/// # Type Parameters
114///
115/// * `'f` - Lifetime of the functions in the bindings
116/// * `Fm` - Function marker type indicating sync/async function support
117pub trait ActivationInterface<'f, Fm: FnMarker = ()> {
118    /// Returns a reference to the variable bindings.
119    ///
120    /// Variable bindings map variable names to their values during evaluation.
121    fn variables(&self) -> &VariableBindings<'f>;
122
123    /// Returns a reference to the function bindings.
124    ///
125    /// Function bindings provide runtime function implementations that can
126    /// override or supplement the functions registered in the environment.
127    fn functions(&self) -> &FunctionBindings<'f>;
128}
129
130/// Activation context for CEL expression evaluation.
131///
132/// An `Activation` provides variable and function bindings that are used
133/// during the evaluation of a CEL expression. It allows you to bind runtime
134/// values to variables and functions that were declared in the environment.
135///
136/// # Type Parameters
137///
138/// * `'f` - Lifetime of the functions in the activation
139/// * `Fm` - Function marker type indicating sync/async function support
140///
141/// # Examples
142///
143/// ## Basic Variable Binding
144///
145/// ```rust,no_run
146/// use cel_cxx::*;
147///
148/// let activation = Activation::new()
149///     .bind_variable("name", "Alice")
150///     .unwrap()
151///     .bind_variable("age", 30i64)
152///     .unwrap();
153/// ```
154///
155/// ## Function Binding
156///
157/// ```rust,no_run
158/// use cel_cxx::*;
159///
160/// let activation = Activation::new()
161///     .bind_global_function("custom_fn", |x: i64| -> i64 { x * 2 })
162///     .unwrap();
163/// ```
164///
165/// ## Empty Activation
166///
167/// For expressions that don't need any bindings, you can use `()`:
168///
169/// ```rust,no_run
170/// use cel_cxx::*;
171///
172/// let env = Env::builder().build().unwrap();
173/// let program = env.compile("1 + 2").unwrap();
174/// let result = program.evaluate(()).unwrap();
175/// ```
176pub struct Activation<'f, Fm: FnMarker = ()> {
177    variables: VariableBindings<'f>,
178    functions: FunctionBindings<'f>,
179    _fn_marker: PhantomData<Fm>,
180}
181
182/// Activation with async support.
183///
184/// This is a convenience type that can be used to create activations with
185/// async support. It is equivalent to `Activation<'f, Async>`.
186///
187/// # Examples
188///
189/// ```rust,no_run
190/// use cel_cxx::*;
191///
192/// let activation = AsyncActivation::new_async();
193/// ```
194#[cfg(feature = "async")]
195#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
196pub type AsyncActivation<'f> = Activation<'f, Async>;
197
198impl<'f> Default for Activation<'f, ()> {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204impl<'f> Activation<'f, ()> {
205    /// Creates a new empty activation.
206    ///
207    /// This creates an activation with no variable or function bindings.
208    /// You can then use the builder methods to add bindings.
209    ///
210    /// # Examples
211    ///
212    /// ```rust,no_run
213    /// use cel_cxx::*;
214    ///
215    /// let activation = Activation::new();
216    /// ```
217    pub fn new() -> Self {
218        Self {
219            variables: VariableBindings::new(),
220            functions: FunctionBindings::new(),
221            _fn_marker: PhantomData,
222        }
223    }
224
225    /// Force the activation to be async.
226    ///
227    /// This method is only available when the `async` feature is enabled.
228    /// It converts the activation to an `AsyncActivation`.
229    ///
230    /// # Examples
231    ///
232    /// ```rust,no_run
233    /// use cel_cxx::*;
234    ///
235    /// let activation = Activation::new().force_async();
236    /// ```
237    #[cfg(feature = "async")]
238    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
239    pub fn force_async(self) -> Activation<'f, Async> {
240        Activation {
241            variables: self.variables,
242            functions: self.functions,
243            _fn_marker: PhantomData,
244        }
245    }
246}
247
248#[cfg(feature = "async")]
249#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
250impl<'f> Activation<'f, Async> {
251    /// Creates a new empty activation with async support.
252    ///
253    /// This creates an activation with no variable or function bindings.
254    /// You can then use the builder methods to add bindings.
255    ///
256    /// # Examples
257    ///
258    /// ```rust,no_run
259    /// use cel_cxx::*;
260    ///
261    /// let activation = Activation::new_async();
262    /// ```
263    pub fn new_async() -> Self {
264        Self {
265            variables: VariableBindings::new(),
266            functions: FunctionBindings::new(),
267            _fn_marker: PhantomData,
268        }
269    }
270}
271
272impl<'f, Fm: FnMarker> Activation<'f, Fm> {
273    /// Binds a variable to a value.
274    ///
275    /// This method adds a variable binding to the activation. The variable
276    /// name must match a variable declared in the environment, and the value
277    /// must be compatible with the declared type.
278    ///
279    /// # Arguments
280    ///
281    /// * `name` - The name of the variable to bind
282    /// * `value` - The value to bind to the variable
283    ///
284    /// # Type Parameters
285    ///
286    /// * `S` - The type of the variable name (must convert to `String`)
287    /// * `T` - The type of the value (must implement `IntoValue` and `TypedValue`)
288    ///
289    /// # Returns
290    ///
291    /// Returns a `Result` containing the updated activation or an error if
292    /// the binding failed.
293    ///
294    /// # Examples
295    ///
296    /// ```rust,no_run
297    /// use cel_cxx::*;
298    ///
299    /// let activation = Activation::new()
300    ///     .bind_variable("name", "World")
301    ///     .unwrap()
302    ///     .bind_variable("count", 42i64)
303    ///     .unwrap();
304    /// ```
305    ///
306    /// # Errors
307    ///
308    /// Returns an error if:
309    /// - The variable name is not declared in the environment
310    /// - The value type doesn't match the declared variable type
311    /// - The value cannot be converted to a CEL value
312    pub fn bind_variable<S, T>(mut self, name: S, value: T) -> Result<Self, Error>
313    where
314        S: Into<String>,
315        T: IntoValue + TypedValue,
316    {
317        self.variables.bind(name, value)?;
318        Ok(Activation {
319            variables: self.variables,
320            functions: self.functions,
321            _fn_marker: PhantomData,
322        })
323    }
324
325    /// Binds a variable to a value provider.
326    ///
327    /// This method allows you to bind a variable to a provider function that
328    /// will be called to get the value when the variable is accessed during
329    /// evaluation. This can be useful for lazy evaluation or for variables
330    /// whose values are expensive to compute.
331    ///
332    /// # Arguments
333    ///
334    /// * `name` - The name of the variable to bind
335    /// * `provider` - The provider function that will supply the value
336    ///
337    /// # Type Parameters
338    ///
339    /// * `S` - The type of the variable name (must convert to `String`)
340    /// * `F` - The provider function type (must implement `IntoFunction`)
341    /// * `Ffm` - The function marker type (sync/async)
342    ///
343    /// # Returns
344    ///
345    /// Returns a `Result` containing the updated activation with the appropriate
346    /// function marker type, or an error if the binding failed.
347    ///
348    /// # Examples
349    ///
350    /// ```rust,no_run
351    /// use cel_cxx::*;
352    ///
353    /// let activation = Activation::new()
354    ///     .bind_variable_provider("timestamp", || -> i64 {
355    ///         std::time::SystemTime::now()
356    ///             .duration_since(std::time::UNIX_EPOCH)
357    ///             .unwrap()
358    ///             .as_secs() as i64
359    ///     })
360    ///     .unwrap();
361    /// ```
362    pub fn bind_variable_provider<S, F, Ffm>(
363        mut self,
364        name: S,
365        provider: F,
366    ) -> Result<Activation<'f, <Ffm as FnMarkerAggr<Fm>>::Output>, Error>
367    where
368        S: Into<String>,
369        F: IntoFunction<'f, Ffm>,
370        Ffm: FnMarkerAggr<Fm>,
371    {
372        self.variables.bind_provider(name, provider)?;
373        Ok(Activation {
374            variables: self.variables,
375            functions: self.functions,
376            _fn_marker: PhantomData,
377        })
378    }
379
380    /// Binds a function (either global or member).
381    ///
382    /// This method allows you to bind a function implementation that can be
383    /// called during expression evaluation. The function can be either a
384    /// global function or a member function.
385    ///
386    /// # Arguments
387    ///
388    /// * `name` - The name of the function to bind
389    /// * `member` - Whether this is a member function (`true`) or global function (`false`)
390    /// * `f` - The function implementation
391    ///
392    /// # Type Parameters
393    ///
394    /// * `S` - The type of the function name (must convert to `String`)
395    /// * `F` - The function implementation type (must implement `IntoFunction`)
396    /// * `Ffm` - The function marker type (sync/async)
397    ///
398    /// # Returns
399    ///
400    /// Returns a `Result` containing the updated activation with the appropriate
401    /// function marker type, or an error if the binding failed.
402    ///
403    /// # Examples
404    ///
405    /// ```rust,no_run
406    /// use cel_cxx::*;
407    ///
408    /// // Bind a global function
409    /// let activation = Activation::new()
410    ///     .bind_function("double", false, |x: i64| -> i64 { x * 2 })
411    ///     .unwrap();
412    ///
413    /// // Bind a member function
414    /// let activation = Activation::new()
415    ///     .bind_function("to_upper", true, |s: String| -> String { s.to_uppercase() })
416    ///     .unwrap();
417    /// ```
418    pub fn bind_function<S, F, Ffm, Args>(
419        mut self,
420        name: S,
421        member: bool,
422        f: F,
423    ) -> Result<Activation<'f, <Ffm as FnMarkerAggr<Fm>>::Output>, Error>
424    where
425        S: Into<String>,
426        F: IntoFunction<'f, Ffm, Args>,
427        Ffm: FnMarkerAggr<Fm>,
428        Args: Arguments,
429    {
430        self.functions.bind(name, member, f)?;
431        Ok(Activation {
432            variables: self.variables,
433            functions: self.functions,
434            _fn_marker: PhantomData,
435        })
436    }
437
438    /// Binds a member function.
439    ///
440    /// This is a convenience method for binding member functions (functions that
441    /// are called as methods on values, like `value.method()`).
442    ///
443    /// # Arguments
444    ///
445    /// * `name` - The name of the function to bind
446    /// * `f` - The function implementation
447    ///
448    /// # Type Parameters
449    ///
450    /// * `S` - The type of the function name (must convert to `String`)
451    /// * `F` - The function implementation type (must implement `IntoFunction`)
452    /// * `Ffm` - The function marker type (sync/async)
453    ///
454    /// # Returns
455    ///
456    /// Returns a `Result` containing the updated activation with the appropriate
457    /// function marker type, or an error if the binding failed.
458    ///
459    /// # Examples
460    ///
461    /// ```rust,no_run
462    /// use cel_cxx::*;
463    ///
464    /// let activation = Activation::new()
465    ///     .bind_member_function("to_upper", |s: String| -> String { s.to_uppercase() })
466    ///     .unwrap();
467    /// ```
468    pub fn bind_member_function<S, F, Ffm, Args>(
469        self,
470        name: S,
471        f: F,
472    ) -> Result<Activation<'f, <Ffm as FnMarkerAggr<Fm>>::Output>, Error>
473    where
474        S: Into<String>,
475        F: IntoFunction<'f, Ffm, Args>,
476        Ffm: FnMarkerAggr<Fm>,
477        Args: Arguments,
478    {
479        self.bind_function(name, true, f)
480    }
481
482    /// Binds a global function.
483    ///
484    /// This is a convenience method for binding global functions (functions that
485    /// can be called from any context).
486    ///
487    /// # Arguments
488    ///
489    /// * `name` - The name of the function to bind
490    /// * `f` - The function implementation
491    ///
492    /// # Type Parameters
493    ///
494    /// * `S` - The type of the function name (must convert to `String`)
495    /// * `F` - The function implementation type (must implement `IntoFunction`)
496    /// * `Ffm` - The function marker type (sync/async)
497    ///
498    /// # Returns
499    ///
500    /// Returns a `Result` containing the updated activation with the appropriate
501    /// function marker type, or an error if the binding failed.
502    ///
503    /// # Examples
504    ///
505    /// ```rust,no_run
506    /// use cel_cxx::*;
507    ///
508    /// let activation = Activation::new()
509    ///     .bind_global_function("double", |x: i64| -> i64 { x * 2 })
510    ///     .unwrap();
511    /// ```
512    pub fn bind_global_function<S, F, Ffm, Args>(
513        self,
514        name: S,
515        f: F,
516    ) -> Result<Activation<'f, <Ffm as FnMarkerAggr<Fm>>::Output>, Error>
517    where
518        S: Into<String>,
519        F: IntoFunction<'f, Ffm, Args>,
520        Ffm: FnMarkerAggr<Fm>,
521        Args: Arguments,
522    {
523        self.bind_function(name, false, f)
524    }
525}
526
527impl<'f, Fm: FnMarker> ActivationInterface<'f, Fm> for Activation<'f, Fm> {
528    fn variables(&self) -> &VariableBindings<'f> {
529        &self.variables
530    }
531
532    fn functions(&self) -> &FunctionBindings<'f> {
533        &self.functions
534    }
535}
536
537impl<'f, Fm: FnMarker> ActivationInterface<'f, Fm> for &Activation<'f, Fm> {
538    fn variables(&self) -> &VariableBindings<'f> {
539        (*self).variables()
540    }
541
542    fn functions(&self) -> &FunctionBindings<'f> {
543        (*self).functions()
544    }
545}
546
547impl<'f, Fm: FnMarker> ActivationInterface<'f, Fm> for std::sync::Arc<Activation<'f, Fm>> {
548    fn variables(&self) -> &VariableBindings<'f> {
549        (**self).variables()
550    }
551
552    fn functions(&self) -> &FunctionBindings<'f> {
553        (**self).functions()
554    }
555}
556
557impl<'f, Fm: FnMarker> ActivationInterface<'f, Fm> for Box<Activation<'f, Fm>> {
558    fn variables(&self) -> &VariableBindings<'f> {
559        (**self).variables()
560    }
561
562    fn functions(&self) -> &FunctionBindings<'f> {
563        (**self).functions()
564    }
565}
566
567static EMPTY_VARIABLES: std::sync::LazyLock<VariableBindings<'static>> =
568    std::sync::LazyLock::new(VariableBindings::new);
569static EMPTY_FUNCTIONS: std::sync::LazyLock<FunctionBindings<'static>> =
570    std::sync::LazyLock::new(FunctionBindings::new);
571
572/// Empty activation implementation for the unit type.
573///
574/// This allows you to use `()` as an activation when no variable or function
575/// bindings are needed.
576impl ActivationInterface<'static> for () {
577    fn variables(&self) -> &VariableBindings<'static> {
578        &EMPTY_VARIABLES
579    }
580
581    fn functions(&self) -> &FunctionBindings<'static> {
582        &EMPTY_FUNCTIONS
583    }
584}
585
586/// Empty activation implementation for references to the unit type.
587impl ActivationInterface<'static> for &() {
588    fn variables(&self) -> &VariableBindings<'static> {
589        &EMPTY_VARIABLES
590    }
591
592    fn functions(&self) -> &FunctionBindings<'static> {
593        &EMPTY_FUNCTIONS
594    }
595}
596
597impl<'f, Fm: FnMarker> std::fmt::Debug for Activation<'f, Fm> {
598    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
599        f.debug_struct("Activation")
600            .field("variables", &self.variables)
601            .field("functions", &self.functions)
602            .finish()
603    }
604}