cel_cxx/program/
mod.rs

1//! Compiled CEL program evaluation.
2//!
3//! This module provides the [`Program`] type, which represents a compiled CEL expression
4//! ready for evaluation. Programs are created by compiling CEL expressions using an
5//! [`Env`](crate::Env) and can be evaluated multiple times with different variable
6//! bindings (activations).
7//!
8//! # Key Features
9//!
10//! - **Compiled expressions**: CEL expressions are parsed and compiled once, then evaluated many times
11//! - **Type safety**: Programs know their return type at compile time
12//! - **Variable binding**: Support for dynamic variable values through activations
13//! - **Async support**: Programs can contain and evaluate async functions
14//! - **Runtime selection**: Choose between different async runtimes (Tokio, async-std)
15//!
16//! # Program Types
17//!
18//! Programs are parameterized by function and runtime markers:
19//!
20//! - **`Program<'f>`**: Synchronous program with sync functions only
21//! - **`AsyncProgram<'f>`**: Program that can contain async functions
22//! - **`Program<'f, Fm, Rm>`**: Full type with function marker `Fm` and runtime marker `Rm`
23//!
24//! # Evaluation Model
25//!
26//! Programs use an activation-based evaluation model:
27//!
28//! 1. **Compilation**: Parse and type-check the CEL expression
29//! 2. **Activation**: Bind variables and functions for a specific evaluation
30//! 3. **Evaluation**: Execute the compiled expression with the bound values
31//!
32//! # Examples
33//!
34//! ## Basic synchronous evaluation
35//!
36//! ```rust,no_run
37//! use cel_cxx::*;
38//!
39//! // Create environment and compile expression
40//! let env = Env::builder()
41//!     .declare_variable::<String>("user_name")?
42//!     .declare_variable::<i64>("user_age")?
43//!     .build()?;
44//!
45//! let program = env.compile("'Hello ' + user_name + ', you are ' + string(user_age)")?;
46//!
47//! // Create activation with variable bindings
48//! let activation = Activation::new()
49//!     .bind_variable("user_name", "Alice".to_string())?
50//!     .bind_variable("user_age", 30i64)?;
51//!
52//! // Evaluate the program
53//! let result = program.evaluate(activation)?;
54//! println!("{}", result); // "Hello Alice, you are 30"
55//! # Ok::<(), cel_cxx::Error>(())
56//! ```
57//!
58//! ## Working with functions
59//!
60//! ```rust,no_run
61//! use cel_cxx::*;
62//!
63//! // Register custom function
64//! let env = Env::builder()
65//!     .register_global_function("multiply", |a: i64, b: i64| a * b)?
66//!     .declare_variable::<i64>("x")?
67//!     .build()?;
68//!
69//! let program = env.compile("multiply(x, 2) + 1")?;
70//!
71//! let activation = Activation::new()
72//!     .bind_variable("x", 21i64)?;
73//!
74//! let result = program.evaluate(activation)?;
75//! assert_eq!(result, Value::Int(43));
76//! # Ok::<(), cel_cxx::Error>(())
77//! ```
78//!
79//! ## Async evaluation
80//!
81//! ```rust,no_run
82//! # #[cfg(feature = "async")]
83//! # async fn example() -> Result<(), cel_cxx::Error> {
84//! use cel_cxx::*;
85//! use cel_cxx::r#async::Tokio;
86//!
87//! // Register async function
88//! let env = Env::builder()
89//!     .register_global_function("fetch_data", |url: String| async move {
90//!         // Simulate async work
91//!         tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
92//!         format!("Data from {}", url)
93//!     })?
94//!     .build()?;
95//!
96//! let program = env.compile("fetch_data('https://api.example.com')")?
97//!     .use_runtime::<Tokio>();
98//!
99//! let result = program.evaluate(()).await?;
100//! println!("{}", result); // "Data from https://api.example.com"
101//! # Ok(())
102//! # }
103//! ```
104//!
105//! ## Reusing programs
106//!
107//! ```rust,no_run
108//! use cel_cxx::*;
109//!
110//! let env = Env::builder()
111//!     .declare_variable::<i64>("value")?
112//!     .build()?;
113//!
114//! // Compile once
115//! let program = env.compile("value * value")?;
116//!
117//! // Evaluate multiple times with different values
118//! for i in 1..=5 {
119//!     let activation = Activation::new()
120//!         .bind_variable("value", i)?;
121//!     
122//!     let result = program.evaluate(activation)?;
123//!     println!("{} * {} = {}", i, i, result);
124//! }
125//! # Ok::<(), cel_cxx::Error>(())
126//! ```
127
128use std::sync::Arc;
129mod eval_dispatch;
130mod inner;
131use super::{ActivationInterface, Error, Value, ValueType};
132use crate::{FnMarker, FnMarkerAggr, FnResult, RuntimeMarker};
133use eval_dispatch::{EvalDispatch, EvalDispatcher};
134
135pub(crate) use inner::ProgramInner;
136
137#[cfg(feature = "async")]
138#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
139use crate::marker::Async;
140
141/// Compiled CEL program ready for evaluation.
142///
143/// A `Program` represents a compiled CEL expression that can be evaluated
144/// multiple times with different variable bindings (activations). Programs
145/// are created by compiling CEL expressions using an [`Env`](crate::Env).
146///
147/// # Type Parameters
148///
149/// - `'f`: Lifetime of functions registered in the environment
150/// - `Fm`: Function marker type indicating sync/async function support
151/// - `Rm`: Runtime marker type indicating the async runtime (if any)
152///
153/// # Examples
154///
155/// ## Basic Usage
156///
157/// ```rust,no_run
158/// use cel_cxx::*;
159///
160/// let env = Env::builder()
161///     .declare_variable::<String>("name")?
162///     .build()?;
163///     
164/// let program = env.compile("'Hello, ' + name")?;
165///
166/// let activation = Activation::new()
167///     .bind_variable("name", "World")?;
168///     
169/// let result = program.evaluate(activation)?;
170/// # Ok::<(), cel_cxx::Error>(())
171/// ```
172///
173/// ## Type Information
174///
175/// ```rust,no_run
176/// use cel_cxx::*;
177///
178/// let env = Env::builder().build()?;
179/// let program = env.compile("42")?;
180///
181/// // Check the return type
182/// println!("Return type: {:?}", program.return_type());
183/// # Ok::<(), cel_cxx::Error>(())
184/// ```
185pub struct Program<'f, Fm: FnMarker = (), Rm: RuntimeMarker = ()> {
186    pub(crate) inner: Arc<ProgramInner<'f>>,
187    pub(crate) _fn_marker: std::marker::PhantomData<Fm>,
188    pub(crate) _rt_marker: std::marker::PhantomData<Rm>,
189}
190
191/// Type alias for asynchronous CEL programs.
192///
193/// This is a convenience type alias for programs that support asynchronous
194/// evaluation with async functions and/or async runtime.
195#[cfg(feature = "async")]
196#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
197pub type AsyncProgram<'f, Rm = ()> = Program<'f, Async, Rm>;
198
199impl<'f, Fm: FnMarker, Rm: RuntimeMarker> Program<'f, Fm, Rm> {
200    /// Returns the return type of this program.
201    ///
202    /// This method returns the CEL type that this program will produce when
203    /// evaluated. The type is determined during compilation based on the
204    /// expression and the declared variables and functions.
205    ///
206    /// # Returns
207    ///
208    /// A reference to the [`ValueType`] that this program returns.
209    ///
210    /// # Examples
211    ///
212    /// ```rust,no_run
213    /// use cel_cxx::*;
214    ///
215    /// let env = Env::builder().build()?;
216    /// let program = env.compile("42")?;
217    ///
218    /// println!("Return type: {:?}", program.return_type());
219    /// // Output: Return type: Int
220    /// # Ok::<(), cel_cxx::Error>(())
221    /// ```
222    pub fn return_type(&self) -> &ValueType {
223        self.inner.return_type()
224    }
225
226    /// Evaluates the program with the given activation.
227    ///
228    /// This method evaluates the compiled CEL expression using the variable
229    /// and function bindings provided in the activation. The return type
230    /// of this method depends on the program and activation markers:
231    ///
232    /// - For synchronous programs: Returns `Result<Value, Error>`
233    /// - For asynchronous programs: Returns `BoxFuture<Result<Value, Error>>`
234    ///
235    /// # Arguments
236    ///
237    /// * `activation` - The activation containing variable and function bindings
238    ///
239    /// # Type Parameters
240    ///
241    /// * `A` - The activation type
242    /// * `Afm` - The activation's function marker type
243    ///
244    /// # Examples
245    ///
246    /// ## Synchronous Evaluation
247    ///
248    /// ```rust,no_run
249    /// use cel_cxx::*;
250    ///
251    /// let env = Env::builder()
252    ///     .declare_variable::<i64>("x")?
253    ///     .build()?;
254    ///     
255    /// let program = env.compile("x * 2")?;
256    ///
257    /// let activation = Activation::new()
258    ///     .bind_variable("x", 21i64)?;
259    ///     
260    /// let result = program.evaluate(activation)?;
261    /// // result == Value::Int(42)
262    /// # Ok::<(), cel_cxx::Error>(())
263    /// ```
264    ///
265    /// ## With Empty Activation
266    ///
267    /// ```rust,no_run
268    /// use cel_cxx::*;
269    ///
270    /// let env = Env::builder().build()?;
271    /// let program = env.compile("1 + 2 * 3")?;
272    ///
273    /// let result = program.evaluate(())?;
274    /// // result == Value::Int(7)
275    /// # Ok::<(), cel_cxx::Error>(())
276    /// ```
277    pub fn evaluate<'a, A, Afm>(
278        &self,
279        activation: A,
280    ) -> <<Afm as FnMarkerAggr<Fm>>::Output as FnResult<'f, Result<Value, Error>>>::Output
281    where
282        'f: 'a,
283        A: ActivationInterface<'f, Afm> + 'a,
284        Afm: FnMarkerAggr<Fm>,
285        <Afm as FnMarkerAggr<Fm>>::Output: FnResult<'f, Result<Value, Error>>,
286        EvalDispatcher<<Afm as FnMarkerAggr<Fm>>::Output, Rm>:
287            EvalDispatch<
288                'f,
289                A,
290                Afm,
291                Output = <<Afm as FnMarkerAggr<Fm>>::Output as FnResult<
292                    'f,
293                    Result<Value, Error>,
294                >>::Output,
295            >,
296    {
297        EvalDispatcher::<<Afm as FnMarkerAggr<Fm>>::Output, Rm>::new()
298            .eval(self.inner.clone(), activation)
299    }
300}
301
302/// Async-specific methods for programs.
303///
304/// These methods are only available when the `async` feature is enabled
305/// and provide utilities for working with async runtimes.
306#[cfg(feature = "async")]
307#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
308const _: () = {
309    use crate::r#async::*;
310
311    impl<'f, Rm: RuntimeMarker> Program<'f, (), Rm> {
312        /// Forces conversion to an async program.
313        ///
314        /// This method converts a synchronous program to an asynchronous one,
315        /// allowing it to work with async functions and evaluation.
316        ///
317        /// # Examples
318        ///
319        /// ```rust,no_run
320        /// # #[cfg(feature = "async")]
321        /// # {
322        /// use cel_cxx::*;
323        ///
324        /// let sync_program = Env::builder().build()?.compile("42")?;
325        /// let async_program = sync_program.force_async();
326        /// # }
327        /// # Ok::<(), cel_cxx::Error>(())
328        /// ```
329        pub fn force_async(self) -> Program<'f, Async, Rm> {
330            Program {
331                inner: self.inner,
332                _fn_marker: std::marker::PhantomData,
333                _rt_marker: std::marker::PhantomData,
334            }
335        }
336    }
337
338    impl<'f, Fm: FnMarker> Program<'f, Fm, ()> {
339        /// Configures this program to use a specific async runtime.
340        ///
341        /// This method allows you to specify which async runtime should be
342        /// used for evaluating this program when it contains async functions.
343        ///
344        /// # Type Parameters
345        ///
346        /// * `Rt` - The runtime type to use
347        ///
348        /// # Examples
349        ///
350        /// ```rust,no_run
351        /// # #[cfg(feature = "tokio")]
352        /// # fn example() -> Result<(), cel_cxx::Error> {
353        /// use cel_cxx::*;
354        /// use cel_cxx::r#async::Tokio;
355        ///
356        /// let env = Env::builder().build()?;
357        /// let program = env.compile("42")?;
358        ///
359        /// let async_program = program.use_runtime::<Tokio>();
360        /// # Ok::<(), cel_cxx::Error>(())
361        /// # }
362        /// ```
363        pub fn use_runtime<Rt: Runtime>(self) -> Program<'f, Fm, Rt> {
364            Program {
365                inner: self.inner,
366                _fn_marker: self._fn_marker,
367                _rt_marker: std::marker::PhantomData,
368            }
369        }
370
371        /// Configures this program to use the Tokio async runtime.
372        ///
373        /// This is a convenience method equivalent to `use_runtime::<Tokio>()`.
374        ///
375        /// # Examples
376        ///
377        /// ```rust,no_run
378        /// # #[cfg(feature = "tokio")]
379        /// # fn example() -> Result<(), cel_cxx::Error> {
380        /// use cel_cxx::*;
381        ///
382        /// let env = Env::builder().build()?;
383        /// let program = env.compile("42")?;
384        ///
385        /// let tokio_program = program.use_tokio();
386        /// # Ok::<(), cel_cxx::Error>(())
387        /// # }
388        /// ```
389        #[cfg(feature = "tokio")]
390        #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
391        pub fn use_tokio(self) -> Program<'f, Fm, Tokio> {
392            self.use_runtime::<Tokio>()
393        }
394
395        /// Configures this program to use the async-std runtime.
396        ///
397        /// This is a convenience method equivalent to `use_runtime::<AsyncStd>()`.
398        ///
399        /// # Examples
400        ///
401        /// ```rust,no_run
402        /// # #[cfg(feature = "async-std")]
403        /// # fn example() -> Result<(), cel_cxx::Error> {
404        /// use cel_cxx::*;
405        ///
406        /// let env = Env::builder().build()?;
407        /// let program = env.compile("42")?;
408        ///
409        /// let async_std_program = program.use_async_std();
410        /// # Ok::<(), cel_cxx::Error>(())
411        /// # }
412        /// ```
413        #[cfg(feature = "async-std")]
414        #[cfg_attr(docsrs, doc(cfg(feature = "async-std")))]
415        pub fn use_async_std(self) -> Program<'f, Fm, AsyncStd> {
416            self.use_runtime::<AsyncStd>()
417        }
418
419        /// Configures this program to use the smol runtime.
420        ///
421        /// This is a convenience method equivalent to `use_runtime::<Smol>()`.
422        ///
423        /// # Examples
424        ///
425        /// ```rust,no_run
426        /// # #[cfg(feature = "smol")]
427        /// # fn example() -> Result<(), cel_cxx::Error> {
428        /// use cel_cxx::*;
429        ///
430        /// let env = Env::builder().build()?;
431        /// let program = env.compile("42")?;
432        ///
433        /// let smol_program = program.use_smol();
434        /// # Ok::<(), cel_cxx::Error>(())
435        /// # }
436        /// ```
437        #[cfg(feature = "smol")]
438        #[cfg_attr(docsrs, doc(cfg(feature = "smol")))]
439        pub fn use_smol(self) -> Program<'f, Fm, Smol> {
440            self.use_runtime::<Smol>()
441        }
442    }
443};
444
445impl<'f, Fm: FnMarker, Rm: RuntimeMarker> std::fmt::Debug for Program<'f, Fm, Rm> {
446    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
447        f.debug_struct("Program")
448            .field("inner", &self.inner)
449            .finish()
450    }
451}
452
453impl<'f, Fm: FnMarker, Rm: RuntimeMarker> Clone for Program<'f, Fm, Rm> {
454    fn clone(&self) -> Self {
455        Program {
456            inner: self.inner.clone(),
457            _fn_marker: self._fn_marker,
458            _rt_marker: self._rt_marker,
459        }
460    }
461}
462
463#[cfg(test)]
464mod test {
465    #![allow(unused)]
466
467    use super::*;
468    use crate::Activation;
469
470    fn assert_eval_type<'f>(program: Program<'f, (), ()>, activation: Activation<'f, ()>) {
471        let _result: Result<Value, Error> = program.evaluate(activation);
472    }
473
474    #[cfg(feature = "async")]
475    const _: () = {
476        use crate::r#async::Tokio;
477        use crate::Async;
478        use futures::future::BoxFuture;
479
480        #[cfg(feature = "tokio")]
481        const _: () = {
482            fn assert_eval_type_async1<'f>(
483                program: Program<'f, Async, Tokio>,
484                activation: Activation<'f, ()>,
485            ) {
486                let _result: BoxFuture<'f, Result<Value, Error>> = program.evaluate(activation);
487            }
488
489            fn assert_eval_type_async2<'f>(
490                program: Program<'f, (), Tokio>,
491                activation: Activation<'f, Async>,
492            ) {
493                let _result: BoxFuture<'f, Result<Value, Error>> = program.evaluate(activation);
494            }
495
496            fn assert_eval_type_async3<'f>(
497                program: Program<'f, Async, Tokio>,
498                activation: Activation<'f, Async>,
499            ) {
500                let _result: BoxFuture<'f, Result<Value, Error>> = program.evaluate(activation);
501            }
502
503            fn assert_eval_type_async4<'f>(
504                program: Program<'f, Async, ()>,
505                activation: Activation<'f, Async>,
506            ) {
507                let _result: BoxFuture<'f, Result<Value, Error>> =
508                    program.use_tokio().evaluate(activation);
509            }
510        };
511    };
512}