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, Fm: FnMarker> Program<'f, Fm, ()> {
312 /// Configures this program to use a specific async runtime.
313 ///
314 /// This method allows you to specify which async runtime should be
315 /// used for evaluating this program when it contains async functions.
316 ///
317 /// # Type Parameters
318 ///
319 /// * `Rt` - The runtime type to use
320 ///
321 /// # Examples
322 ///
323 /// ```rust,no_run
324 /// # #[cfg(feature = "tokio")]
325 /// # fn example() -> Result<(), cel_cxx::Error> {
326 /// use cel_cxx::*;
327 /// use cel_cxx::r#async::Tokio;
328 ///
329 /// let env = Env::builder().build()?;
330 /// let program = env.compile("42")?;
331 ///
332 /// let async_program = program.use_runtime::<Tokio>();
333 /// # Ok::<(), cel_cxx::Error>(())
334 /// # }
335 /// ```
336 pub fn use_runtime<Rt: Runtime>(self) -> Program<'f, Fm, Rt> {
337 Program {
338 inner: self.inner,
339 _fn_marker: self._fn_marker,
340 _rt_marker: std::marker::PhantomData,
341 }
342 }
343
344 /// Configures this program to use the Tokio async runtime.
345 ///
346 /// This is a convenience method equivalent to `use_runtime::<Tokio>()`.
347 ///
348 /// # Examples
349 ///
350 /// ```rust,no_run
351 /// # #[cfg(feature = "tokio")]
352 /// # fn example() -> Result<(), cel_cxx::Error> {
353 /// use cel_cxx::*;
354 ///
355 /// let env = Env::builder().build()?;
356 /// let program = env.compile("42")?;
357 ///
358 /// let tokio_program = program.use_tokio();
359 /// # Ok::<(), cel_cxx::Error>(())
360 /// # }
361 /// ```
362 #[cfg(feature = "tokio")]
363 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
364 pub fn use_tokio(self) -> Program<'f, Fm, Tokio> {
365 self.use_runtime::<Tokio>()
366 }
367
368 /// Configures this program to use the async-std runtime.
369 ///
370 /// This is a convenience method equivalent to `use_runtime::<AsyncStd>()`.
371 ///
372 /// # Examples
373 ///
374 /// ```rust,no_run
375 /// # #[cfg(feature = "async-std")]
376 /// # fn example() -> Result<(), cel_cxx::Error> {
377 /// use cel_cxx::*;
378 ///
379 /// let env = Env::builder().build()?;
380 /// let program = env.compile("42")?;
381 ///
382 /// let async_std_program = program.use_async_std();
383 /// # Ok::<(), cel_cxx::Error>(())
384 /// # }
385 /// ```
386 #[cfg(feature = "async-std")]
387 #[cfg_attr(docsrs, doc(cfg(feature = "async-std")))]
388 pub fn use_async_std(self) -> Program<'f, Fm, AsyncStd> {
389 self.use_runtime::<AsyncStd>()
390 }
391 }
392};
393
394impl<'f, Fm: FnMarker, Rm: RuntimeMarker> std::fmt::Debug for Program<'f, Fm, Rm> {
395 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396 f.debug_struct("Program")
397 .field("inner", &self.inner)
398 .finish()
399 }
400}
401
402impl<'f, Fm: FnMarker, Rm: RuntimeMarker> Clone for Program<'f, Fm, Rm> {
403 fn clone(&self) -> Self {
404 Program {
405 inner: self.inner.clone(),
406 _fn_marker: self._fn_marker,
407 _rt_marker: self._rt_marker,
408 }
409 }
410}
411
412#[cfg(test)]
413mod test {
414 #![allow(unused)]
415
416 use super::*;
417 use crate::Activation;
418
419 fn assert_eval_type<'f>(program: Program<'f, (), ()>, activation: Activation<'f, ()>) {
420 let _result: Result<Value, Error> = program.evaluate(activation);
421 }
422
423 #[cfg(feature = "async")]
424 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
425 const _: () = {
426 use crate::r#async::Tokio;
427 use crate::Async;
428 use futures::future::BoxFuture;
429
430 #[cfg(feature = "tokio")]
431 const _: () = {
432 fn assert_eval_type_async1<'f>(
433 program: Program<'f, Async, Tokio>,
434 activation: Activation<'f, ()>,
435 ) {
436 let _result: BoxFuture<'f, Result<Value, Error>> = program.evaluate(activation);
437 }
438
439 fn assert_eval_type_async2<'f>(
440 program: Program<'f, (), Tokio>,
441 activation: Activation<'f, Async>,
442 ) {
443 let _result: BoxFuture<'f, Result<Value, Error>> = program.evaluate(activation);
444 }
445
446 fn assert_eval_type_async3<'f>(
447 program: Program<'f, Async, Tokio>,
448 activation: Activation<'f, Async>,
449 ) {
450 let _result: BoxFuture<'f, Result<Value, Error>> = program.evaluate(activation);
451 }
452
453 fn assert_eval_type_async4<'f>(
454 program: Program<'f, Async, ()>,
455 activation: Activation<'f, Async>,
456 ) {
457 let _result: BoxFuture<'f, Result<Value, Error>> =
458 program.use_tokio().evaluate(activation);
459 }
460 };
461 };
462}