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}