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}