pub struct EnvBuilder<'f, Fm: FnMarker = (), Rm: RuntimeMarker = ()> { /* private fields */ }
Expand description
Builder for creating CEL environments.
The EnvBuilder
allows you to configure a CEL environment by registering
functions, declaring variables, and setting up runtime options before
building the final environment.
§Type Parameters
'f
: Lifetime of functions that will be registeredFm
: Function marker type indicating sync/async function supportRm
: Runtime marker type indicating the async runtime (if any)
§Examples
use cel_cxx::*;
let env = Env::builder()
.register_global_function("double", |x: i64| -> i64 { x * 2 })?
.declare_variable::<String>("message")?
.build()?;
Implementations§
Source§impl<'f, Fm: FnMarker, Rm: RuntimeMarker> EnvBuilder<'f, Fm, Rm>
impl<'f, Fm: FnMarker, Rm: RuntimeMarker> EnvBuilder<'f, Fm, Rm>
Source§impl<'f, Fm: FnMarker, Rm: RuntimeMarker> EnvBuilder<'f, Fm, Rm>
impl<'f, Fm: FnMarker, Rm: RuntimeMarker> EnvBuilder<'f, Fm, Rm>
Sourcepub fn register_function<F, Ffm, Args>(
self,
name: impl Into<String>,
member: bool,
f: F,
) -> Result<EnvBuilder<'f, <Ffm as FnMarkerAggr<Fm>>::Output, Rm>, Error>
pub fn register_function<F, Ffm, Args>( self, name: impl Into<String>, member: bool, f: F, ) -> Result<EnvBuilder<'f, <Ffm as FnMarkerAggr<Fm>>::Output, Rm>, Error>
Registers a function (either global or member).
This method allows you to register custom functions that can be called from CEL expressions. The function can be either a global function or a member function of a type.
§Function Registration Process
When you register a function, the system:
- Extracts type information from the function signature
- Creates type-safe conversion wrappers
- Stores both the type signature and implementation
- Updates the function marker type to track sync/async status
§Zero-Annotation Benefits
Functions are registered without explicit type annotations:
- Argument types are automatically inferred
- Return types are automatically determined
- Error handling is automatically supported for
Result<T, E>
returns - Reference parameters like
&str
are handled safely
§Arguments
name
- The name of the function as it will appear in CEL expressionsmember
- Whether this is a member function (true
) or global function (false
)f
- The function implementation (function pointer, closure, etc.)
§Type Parameters
F
- The function implementation typeFfm
- The function marker type (sync/async) inferred from the functionArgs
- The argument tuple type (automatically inferred)
§Returns
A new EnvBuilder
with updated function marker type. If this is the first
async function registered, the marker changes from ()
to Async
.
§Member vs Global Functions
§Global Functions
Called as function_name(args...)
:
max(a, b) // max function with two arguments
calculate(x, y, z) // calculate function with three arguments
§Member Functions
Called as object.method(args...)
:
text.contains(substring) // contains method on string
list.size() // size method on list
§Function Signature Support
Supports various function signatures:
- Simple functions:
fn(T) -> U
- Functions with errors:
fn(T) -> Result<U, E>
- Reference parameters:
fn(&str, i64) -> String
- Multiple parameters: Up to 10 parameters supported
- Closures: Move closures that capture environment
§Errors
Returns Error
if:
- Function name conflicts with existing registration
- Function signature is invalid or unsupported
- Type inference fails
§Examples
§Basic Functions
use cel_cxx::*;
let builder = Env::builder()
.register_function("add", false, |a: i64, b: i64| a + b)?
.register_function("greet", false, |name: &str| format!("Hello, {}!", name))?;
§Member Functions
use cel_cxx::*;
let builder = Env::builder()
.register_function("contains", true, |text: &str, substr: &str| text.contains(substr))?
.register_function("length", true, |text: &str| text.len() as i64)?;
// Usage in expressions:
// text.contains("hello")
// text.length()
§Functions with Error Handling
use cel_cxx::*;
let builder = Env::builder()
.register_function("divide", false, |a: f64, b: f64| -> Result<f64, Error> {
if b == 0.0 {
Err(Error::invalid_argument("division by zero"))
} else {
Ok(a / b)
}
})?;
§Closures with Captured Data
use cel_cxx::*;
let multiplier = 5;
let threshold = 100.0;
let builder = Env::builder()
.register_function("scale", false, move |x: i64| x * multiplier)?
.register_function("check_limit", false, move |value: f64| value < threshold)?;
Sourcepub fn register_member_function<F, Ffm, Args>(
self,
name: impl Into<String>,
f: F,
) -> Result<EnvBuilder<'f, <Ffm as FnMarkerAggr<Fm>>::Output, Rm>, Error>
pub fn register_member_function<F, Ffm, Args>( self, name: impl Into<String>, f: F, ) -> Result<EnvBuilder<'f, <Ffm as FnMarkerAggr<Fm>>::Output, Rm>, Error>
Registers a member function.
This is a convenience method for registering member functions, equivalent to
calling register_function(name, true, f)
. Member functions are called using
dot notation in CEL expressions: object.method(args...)
.
§Arguments
name
- The method name as it will appear in CEL expressionsf
- The function implementation
§Member Function Semantics
Member functions in CEL follow these patterns:
- First parameter is the “receiver” (the object before the dot)
- Additional parameters become method arguments
- Called as
receiver.method(arg1, arg2, ...)
§Examples
§String Methods
use cel_cxx::*;
let builder = Env::builder()
.register_member_function("upper", |s: &str| s.to_uppercase())?
.register_member_function("contains", |s: &str, substr: &str| s.contains(substr))?
.register_member_function("repeat", |s: &str, n: i64| s.repeat(n as usize))?;
// Usage in expressions:
// "hello".upper() -> "HELLO"
// "hello world".contains("world") -> true
// "abc".repeat(3) -> "abcabcabc"
§Numeric Methods
use cel_cxx::*;
let builder = Env::builder()
.register_member_function("abs", |x: f64| x.abs())?
.register_member_function("pow", |x: f64, exp: f64| x.powf(exp))?;
// Usage in expressions:
// (-5.5).abs() -> 5.5
// (2.0).pow(3.0) -> 8.0
Examples found in repository?
269fn demo3_opaque_member_functions() -> Result<(), Error> {
270 println!("📌 Demo 3: Opaque Types & Member Functions");
271
272 let env = Env::builder()
273 .declare_variable::<Student>("student")?
274 // ✨ Register struct methods directly using RustType::method_name syntax
275 .register_member_function("get_name", Student::get_name)?
276 .register_member_function("get_age", Student::get_age)?
277 .register_member_function("get_grade", Student::get_grade)?
278 .register_member_function("is_passing", Student::is_passing)?
279 .register_member_function("has_subject", Student::has_subject)?
280 .register_member_function("get_letter_grade", Student::get_letter_grade)?
281 .build()?;
282
283 let student = Student {
284 name: "John Doe".to_string(),
285 age: 18,
286 grade: 87.5,
287 subjects: vec!["Math".to_string(), "Physics".to_string()],
288 };
289
290 let activation = Activation::new().bind_variable("student", student)?;
291
292 let test_expressions = vec![
293 ("student.get_name()", "Get student name"),
294 ("student.get_age()", "Get student age"),
295 ("student.get_grade()", "Get numerical grade"),
296 ("student.get_letter_grade()", "Get letter grade"),
297 ("student.is_passing()", "Check if passing"),
298 ("student.has_subject('Math')", "Check if has Math subject"),
299 (
300 "student.has_subject('Chemistry')",
301 "Check if has Chemistry subject",
302 ),
303 ];
304
305 for (expr, description) in test_expressions {
306 let program = env.compile(expr)?;
307 let result = program.evaluate(&activation)?;
308 println!(" {} = {} ({})", expr, result, description);
309 }
310
311 println!();
312 Ok(())
313}
314
315/// Demo 4: Type conversions and standard Rust types
316fn demo4_type_conversions() -> Result<(), Error> {
317 println!("📌 Demo 4: Type Conversions & Standard Rust Types");
318
319 // Functions returning different types (both direct values and Results)
320 fn return_string_direct() -> String {
321 "hello world".to_string()
322 }
323 fn return_int_result() -> Result<i64, std::io::Error> {
324 Ok(42)
325 }
326 fn return_list() -> Vec<i64> {
327 vec![1, 2, 3, 4, 5]
328 }
329 fn return_map() -> HashMap<String, i64> {
330 let mut map = HashMap::new();
331 map.insert("key1".to_string(), 100);
332 map.insert("key2".to_string(), 200);
333 map
334 }
335 fn return_optional_some() -> Option<String> {
336 Some("optional value".to_string())
337 }
338 fn return_optional_none() -> Option<String> {
339 None
340 }
341
342 let env = Env::builder()
343 .register_global_function("return_string_direct", return_string_direct)?
344 .register_global_function("return_int_result", return_int_result)?
345 .register_global_function("return_list", return_list)?
346 .register_global_function("return_map", return_map)?
347 .register_global_function("return_optional_some", return_optional_some)?
348 .register_global_function("return_optional_none", return_optional_none)?
349 .build()?;
350
351 let test_cases = vec![
352 ("return_string_direct()", "String conversion"),
353 ("return_int_result()", "Result<i64> conversion"),
354 ("return_list()", "Vec<i64> conversion"),
355 ("return_map()", "HashMap conversion"),
356 ("return_optional_some()", "Option<String> Some conversion"),
357 ("return_optional_none()", "Option<String> None conversion"),
358 ];
359
360 for (expr, description) in test_cases {
361 let program = env.compile(expr)?;
362 let result = program.evaluate(())?;
363 println!(" {} = {} ({})", expr, result, description);
364
365 // Demonstrate type conversion back to Rust types
366 match expr {
367 "return_string_direct()" => {
368 let rust_string: String = result
369 .try_into()
370 .map_err(|_| Error::invalid_argument("string conversion failed".to_string()))?;
371 println!(" Converted back to Rust String: '{}'", rust_string);
372 }
373 "return_int_result()" => {
374 let rust_int: i64 = result
375 .try_into()
376 .map_err(|_| Error::invalid_argument("int conversion failed".to_string()))?;
377 println!(" Converted back to Rust i64: {}", rust_int);
378 }
379 "return_list()" => {
380 let rust_list: Vec<i64> = result
381 .try_into()
382 .map_err(|_| Error::invalid_argument("list conversion failed".to_string()))?;
383 println!(" Converted back to Rust Vec<i64>: {:?}", rust_list);
384 }
385 _ => {}
386 }
387 }
388
389 println!();
390 Ok(())
391}
392
393/// Demo 5: Generic functions and type annotations
394fn demo5_generic_functions() -> Result<(), Error> {
395 println!("📌 Demo 5: Generic Functions & Function Overloads");
396
397 // Generic function that counts items in any Vec<T>
398 fn count_items<T>(items: Vec<T>) -> i64 {
399 items.len() as i64
400 }
401
402 // Generic function that processes maps
403 fn get_map_size<K, V>(map: HashMap<K, V>) -> i64 {
404 map.len() as i64
405 }
406
407 // Function working with references in containers
408 fn join_strings(strings: Vec<&str>, separator: &str) -> String {
409 strings.join(separator)
410 }
411
412 let env = Env::builder()
413 .declare_variable::<Vec<String>>("string_list")?
414 .declare_variable::<Vec<i64>>("int_list")?
415 .declare_variable::<HashMap<String, i64>>("score_map")?
416 .declare_variable::<Vec<f64>>("floats")?
417 // Register generic functions with specific type annotations
418 .register_global_function("count_strings", count_items::<String>)?
419 .register_global_function("count_ints", count_items::<i64>)?
420 .register_global_function("get_string_map_size", get_map_size::<String, i64>)?
421 .register_global_function("join_strings", join_strings)?
422 // Multiple functions with same name, different signatures (overloads)
423 .register_global_function("process", |x: i64| x * 2)?
424 .register_global_function("process", |x: f64| (x * 2.0).round())?
425 .register_global_function("process", |x: String| x.to_uppercase())?
426 // Overloaded member functions for different container types
427 .register_member_function("sum", |numbers: Vec<i64>| numbers.iter().sum::<i64>())?
428 .register_member_function("sum", |floats: Vec<f64>| floats.iter().sum::<f64>())?
429 .build()?;
430
431 let test_cases = vec![
432 // Generic function tests
433 ("count_strings(string_list)", "Count strings in list"),
434 ("count_ints(int_list)", "Count integers in list"),
435 ("get_string_map_size(score_map)", "Get map size"),
436 ("join_strings(string_list, ' ')", "Join strings with space"),
437 // Function overload tests
438 ("process(42)", "Process integer (multiply by 2)"),
439 ("process(3.14)", "Process float (multiply by 2, round)"),
440 ("process('hello')", "Process string (uppercase)"),
441 ("int_list.sum()", "Sum integers"),
442 ("floats.sum()", "Sum floats"),
443 ];
444
445 let activation = Activation::new()
446 .bind_variable(
447 "string_list",
448 vec!["hello".to_string(), "world".to_string(), "rust".to_string()],
449 )?
450 .bind_variable("int_list", vec![1, 2, 3, 4, 5, 6])?
451 .bind_variable("floats", vec![1.5, 2.7, 3.143, 4.0])?
452 .bind_variable("score_map", {
453 let mut map = HashMap::new();
454 map.insert("alice".to_string(), 95);
455 map.insert("bob".to_string(), 87);
456 map.insert("charlie".to_string(), 92);
457 map
458 })?;
459
460 for (expr, description) in test_cases {
461 let program = env.compile(expr)?;
462 let result = program.evaluate(&activation)?;
463 println!(" {} = {} ({})", expr, result, description);
464 }
465
466 println!();
467 Ok(())
468}
Sourcepub fn register_global_function<F, Ffm, Args>(
self,
name: impl Into<String>,
f: F,
) -> Result<EnvBuilder<'f, <Ffm as FnMarkerAggr<Fm>>::Output, Rm>, Error>
pub fn register_global_function<F, Ffm, Args>( self, name: impl Into<String>, f: F, ) -> Result<EnvBuilder<'f, <Ffm as FnMarkerAggr<Fm>>::Output, Rm>, Error>
Registers a global function.
This is a convenience method for registering global functions, equivalent to
calling register_function(name, false, f)
. Global functions are called directly
by name in CEL expressions: function_name(args...)
.
§Arguments
name
- The function name as it will appear in CEL expressionsf
- The function implementation
§Global Function Characteristics
Global functions:
- Are called directly by name without a receiver object
- Can have 0 to 10 parameters
- Support all CEL-compatible parameter and return types
- Can capture environment variables (for closures)
§Function Naming Guidelines
- Use clear, descriptive names:
calculate_tax
,format_date
- Follow CEL naming conventions (snake_case is recommended)
- Avoid conflicts with built-in CEL functions
- Consider namespacing for domain-specific functions:
math_sqrt
,string_trim
§Examples
§Mathematical Functions
use cel_cxx::*;
let builder = Env::builder()
.register_global_function("add", |a: i64, b: i64| a + b)?
.register_global_function("multiply", |a: f64, b: f64| a * b)?
.register_global_function("max", |a: i64, b: i64| if a > b { a } else { b })?;
// Usage in expressions:
// add(10, 20) -> 30
// multiply(2.5, 4.0) -> 10.0
// max(15, 8) -> 15
§String Processing Functions
use cel_cxx::*;
let builder = Env::builder()
.register_global_function("concat", |a: &str, b: &str| format!("{}{}", a, b))?
.register_global_function("trim_prefix", |s: &str, prefix: &str| {
s.strip_prefix(prefix).unwrap_or(s).to_string()
})?;
// Usage in expressions:
// concat("Hello, ", "World!") -> "Hello, World!"
// trim_prefix("prefixed_text", "prefixed_") -> "text"
§Business Logic Functions
use cel_cxx::*;
let builder = Env::builder()
.register_global_function("calculate_discount", |price: f64, rate: f64| {
price * (1.0 - rate.min(1.0).max(0.0))
})?
.register_global_function("is_valid_email", |email: &str| {
email.contains('@') && email.contains('.')
})?;
// Usage in expressions:
// calculate_discount(100.0, 0.15) -> 85.0
// is_valid_email("user@domain.com") -> true
§Functions with Complex Logic
use cel_cxx::*;
use std::collections::HashMap;
// Function that processes collections
let builder = Env::builder()
.register_global_function("sum_positive", |numbers: Vec<i64>| {
numbers.iter().filter(|&x| *x > 0).sum::<i64>()
})?;
// Usage in expressions:
// sum_positive([1, -2, 3, -4, 5]) -> 9
Examples found in repository?
7fn main() -> Result<(), Error> {
8 println!("🚀 CEL-CXX Basic Example\n");
9
10 // Create an environment with variables and functions
11 let env = Env::builder()
12 .declare_variable::<String>("name")?
13 .declare_variable::<i64>("age")?
14 .register_global_function("greet", |name: &str| format!("Hello, {}!", name))?
15 .register_global_function("is_adult", |age: i64| age >= 18)?
16 .build()?;
17
18 // Compile and evaluate expressions
19 let expressions = vec![
20 "greet(name)",
21 "is_adult(age)",
22 "'Name: ' + name + ', Age: ' + string(age)",
23 "age >= 18 ? 'adult' : 'minor'",
24 ];
25
26 let activation = Activation::new()
27 .bind_variable("name", "Alice")?
28 .bind_variable("age", 25i64)?;
29
30 for expr in expressions {
31 let program = env.compile(expr)?;
32 let result = program.evaluate(&activation)?;
33 println!("{} = {}", expr, result);
34 }
35
36 println!("\n✅ Basic example completed!");
37 Ok(())
38}
More examples
146fn demo1_basic_operations() -> Result<(), Error> {
147 println!("📌 Demo 1: Basic Expressions & Zero-Annotation Functions");
148
149 let env = Env::builder()
150 .declare_variable::<String>("name")?
151 .declare_variable::<i64>("age")?
152 .declare_variable::<f64>("score")?
153 // ✨ Zero-annotation functions - types automatically inferred!
154 .register_global_function("greet", |name: &str| format!("Hello, {}!", name))?
155 .register_global_function("is_adult", |age: i64| age >= 18)?
156 .register_global_function("grade", |score: f64| -> String {
157 match score {
158 90.0..=100.0 => "A".to_string(),
159 80.0..=89.9 => "B".to_string(),
160 70.0..=79.9 => "C".to_string(),
161 60.0..=69.9 => "D".to_string(),
162 _ => "F".to_string(),
163 }
164 })?
165 .register_global_function("calculate_discount", |age: i64, score: f64| -> f64 {
166 let base_discount = if age >= 65 { 0.2 } else { 0.0 };
167 let score_bonus = if score >= 90.0 { 0.1 } else { 0.0 };
168 base_discount + score_bonus
169 })?
170 .build()?;
171
172 // Test basic expressions including simple arithmetic
173 let test_cases = vec![
174 ("1 + 1", "Alice", 25i64, 95.0, "Simple arithmetic"),
175 (
176 "greet(name)",
177 "Alice",
178 25i64,
179 95.0,
180 "Function with string parameter",
181 ),
182 ("is_adult(age)", "Bob", 16i64, 85.0, "Boolean function"),
183 (
184 "grade(score)",
185 "Charlie",
186 30i64,
187 78.5,
188 "String return function",
189 ),
190 (
191 "calculate_discount(age, score)",
192 "Diana",
193 67i64,
194 92.0,
195 "Multi-parameter function",
196 ),
197 ];
198
199 for (expr, name, age, score, description) in test_cases {
200 let program = env.compile(expr)?;
201 let activation = Activation::new()
202 .bind_variable("name", name)?
203 .bind_variable("age", age)?
204 .bind_variable("score", score)?;
205
206 let result = program.evaluate(&activation)?;
207 println!(" {} = {} ({})", expr, result, description);
208 }
209
210 println!();
211 Ok(())
212}
213
214/// Demo 2: Variable binding and providers
215fn demo2_variable_operations() -> Result<(), Error> {
216 println!("📌 Demo 2: Variable Binding & Providers");
217
218 let env = Env::builder()
219 .declare_variable::<i64>("a")?
220 .declare_variable::<i64>("b")?
221 .declare_global_function::<fn() -> i64>("get_const")?
222 .register_global_function("multiply", |x: i64, y: i64| x * y)?
223 .build()?;
224
225 // Test direct variable binding
226 {
227 println!(" Variable binding:");
228 let program = env.compile("a + b")?;
229 let activation = Activation::new()
230 .bind_variable("a", 10)?
231 .bind_variable("b", 20)?;
232 let result = program.evaluate(&activation)?;
233 println!(" a + b = {} (direct binding)", result);
234 }
235
236 // Test variable provider binding
237 {
238 println!(" Variable provider binding:");
239 let program = env.compile("a * b")?;
240 let activation = Activation::new()
241 .bind_variable("a", 5)?
242 .bind_variable_provider("b", || -> Result<i64, Error> {
243 println!(" Provider called for variable 'b'");
244 Ok(7)
245 })?;
246 let result = program.evaluate(&activation)?;
247 println!(" a * b = {} (provider binding)", result);
248 }
249
250 // Test function declaration and binding
251 {
252 println!(" Function declaration & binding:");
253 let program = env.compile("get_const() + multiply(a, 3)")?;
254 let activation = Activation::new()
255 .bind_variable("a", 4)?
256 .bind_global_function("get_const", || -> Result<i64, Error> { Ok(100) })?;
257 let result = program.evaluate(&activation)?;
258 println!(
259 " get_const() + multiply(a, 3) = {} (function binding)",
260 result
261 );
262 }
263
264 println!();
265 Ok(())
266}
267
268/// Demo 3: Opaque types with member functions
269fn demo3_opaque_member_functions() -> Result<(), Error> {
270 println!("📌 Demo 3: Opaque Types & Member Functions");
271
272 let env = Env::builder()
273 .declare_variable::<Student>("student")?
274 // ✨ Register struct methods directly using RustType::method_name syntax
275 .register_member_function("get_name", Student::get_name)?
276 .register_member_function("get_age", Student::get_age)?
277 .register_member_function("get_grade", Student::get_grade)?
278 .register_member_function("is_passing", Student::is_passing)?
279 .register_member_function("has_subject", Student::has_subject)?
280 .register_member_function("get_letter_grade", Student::get_letter_grade)?
281 .build()?;
282
283 let student = Student {
284 name: "John Doe".to_string(),
285 age: 18,
286 grade: 87.5,
287 subjects: vec!["Math".to_string(), "Physics".to_string()],
288 };
289
290 let activation = Activation::new().bind_variable("student", student)?;
291
292 let test_expressions = vec![
293 ("student.get_name()", "Get student name"),
294 ("student.get_age()", "Get student age"),
295 ("student.get_grade()", "Get numerical grade"),
296 ("student.get_letter_grade()", "Get letter grade"),
297 ("student.is_passing()", "Check if passing"),
298 ("student.has_subject('Math')", "Check if has Math subject"),
299 (
300 "student.has_subject('Chemistry')",
301 "Check if has Chemistry subject",
302 ),
303 ];
304
305 for (expr, description) in test_expressions {
306 let program = env.compile(expr)?;
307 let result = program.evaluate(&activation)?;
308 println!(" {} = {} ({})", expr, result, description);
309 }
310
311 println!();
312 Ok(())
313}
314
315/// Demo 4: Type conversions and standard Rust types
316fn demo4_type_conversions() -> Result<(), Error> {
317 println!("📌 Demo 4: Type Conversions & Standard Rust Types");
318
319 // Functions returning different types (both direct values and Results)
320 fn return_string_direct() -> String {
321 "hello world".to_string()
322 }
323 fn return_int_result() -> Result<i64, std::io::Error> {
324 Ok(42)
325 }
326 fn return_list() -> Vec<i64> {
327 vec![1, 2, 3, 4, 5]
328 }
329 fn return_map() -> HashMap<String, i64> {
330 let mut map = HashMap::new();
331 map.insert("key1".to_string(), 100);
332 map.insert("key2".to_string(), 200);
333 map
334 }
335 fn return_optional_some() -> Option<String> {
336 Some("optional value".to_string())
337 }
338 fn return_optional_none() -> Option<String> {
339 None
340 }
341
342 let env = Env::builder()
343 .register_global_function("return_string_direct", return_string_direct)?
344 .register_global_function("return_int_result", return_int_result)?
345 .register_global_function("return_list", return_list)?
346 .register_global_function("return_map", return_map)?
347 .register_global_function("return_optional_some", return_optional_some)?
348 .register_global_function("return_optional_none", return_optional_none)?
349 .build()?;
350
351 let test_cases = vec![
352 ("return_string_direct()", "String conversion"),
353 ("return_int_result()", "Result<i64> conversion"),
354 ("return_list()", "Vec<i64> conversion"),
355 ("return_map()", "HashMap conversion"),
356 ("return_optional_some()", "Option<String> Some conversion"),
357 ("return_optional_none()", "Option<String> None conversion"),
358 ];
359
360 for (expr, description) in test_cases {
361 let program = env.compile(expr)?;
362 let result = program.evaluate(())?;
363 println!(" {} = {} ({})", expr, result, description);
364
365 // Demonstrate type conversion back to Rust types
366 match expr {
367 "return_string_direct()" => {
368 let rust_string: String = result
369 .try_into()
370 .map_err(|_| Error::invalid_argument("string conversion failed".to_string()))?;
371 println!(" Converted back to Rust String: '{}'", rust_string);
372 }
373 "return_int_result()" => {
374 let rust_int: i64 = result
375 .try_into()
376 .map_err(|_| Error::invalid_argument("int conversion failed".to_string()))?;
377 println!(" Converted back to Rust i64: {}", rust_int);
378 }
379 "return_list()" => {
380 let rust_list: Vec<i64> = result
381 .try_into()
382 .map_err(|_| Error::invalid_argument("list conversion failed".to_string()))?;
383 println!(" Converted back to Rust Vec<i64>: {:?}", rust_list);
384 }
385 _ => {}
386 }
387 }
388
389 println!();
390 Ok(())
391}
392
393/// Demo 5: Generic functions and type annotations
394fn demo5_generic_functions() -> Result<(), Error> {
395 println!("📌 Demo 5: Generic Functions & Function Overloads");
396
397 // Generic function that counts items in any Vec<T>
398 fn count_items<T>(items: Vec<T>) -> i64 {
399 items.len() as i64
400 }
401
402 // Generic function that processes maps
403 fn get_map_size<K, V>(map: HashMap<K, V>) -> i64 {
404 map.len() as i64
405 }
406
407 // Function working with references in containers
408 fn join_strings(strings: Vec<&str>, separator: &str) -> String {
409 strings.join(separator)
410 }
411
412 let env = Env::builder()
413 .declare_variable::<Vec<String>>("string_list")?
414 .declare_variable::<Vec<i64>>("int_list")?
415 .declare_variable::<HashMap<String, i64>>("score_map")?
416 .declare_variable::<Vec<f64>>("floats")?
417 // Register generic functions with specific type annotations
418 .register_global_function("count_strings", count_items::<String>)?
419 .register_global_function("count_ints", count_items::<i64>)?
420 .register_global_function("get_string_map_size", get_map_size::<String, i64>)?
421 .register_global_function("join_strings", join_strings)?
422 // Multiple functions with same name, different signatures (overloads)
423 .register_global_function("process", |x: i64| x * 2)?
424 .register_global_function("process", |x: f64| (x * 2.0).round())?
425 .register_global_function("process", |x: String| x.to_uppercase())?
426 // Overloaded member functions for different container types
427 .register_member_function("sum", |numbers: Vec<i64>| numbers.iter().sum::<i64>())?
428 .register_member_function("sum", |floats: Vec<f64>| floats.iter().sum::<f64>())?
429 .build()?;
430
431 let test_cases = vec![
432 // Generic function tests
433 ("count_strings(string_list)", "Count strings in list"),
434 ("count_ints(int_list)", "Count integers in list"),
435 ("get_string_map_size(score_map)", "Get map size"),
436 ("join_strings(string_list, ' ')", "Join strings with space"),
437 // Function overload tests
438 ("process(42)", "Process integer (multiply by 2)"),
439 ("process(3.14)", "Process float (multiply by 2, round)"),
440 ("process('hello')", "Process string (uppercase)"),
441 ("int_list.sum()", "Sum integers"),
442 ("floats.sum()", "Sum floats"),
443 ];
444
445 let activation = Activation::new()
446 .bind_variable(
447 "string_list",
448 vec!["hello".to_string(), "world".to_string(), "rust".to_string()],
449 )?
450 .bind_variable("int_list", vec![1, 2, 3, 4, 5, 6])?
451 .bind_variable("floats", vec![1.5, 2.7, 3.143, 4.0])?
452 .bind_variable("score_map", {
453 let mut map = HashMap::new();
454 map.insert("alice".to_string(), 95);
455 map.insert("bob".to_string(), 87);
456 map.insert("charlie".to_string(), 92);
457 map
458 })?;
459
460 for (expr, description) in test_cases {
461 let program = env.compile(expr)?;
462 let result = program.evaluate(&activation)?;
463 println!(" {} = {} ({})", expr, result, description);
464 }
465
466 println!();
467 Ok(())
468}
469
470/// Demo 6: Error handling with different error types including Box<dyn std::error::Error>
471fn demo6_error_handling() -> Result<(), Error> {
472 println!("📌 Demo 6: Error Handling & Validation");
473
474 let env = Env::builder()
475 .declare_variable::<String>("input")?
476 .declare_variable::<i64>("divisor")?
477 .declare_variable::<String>("email")?
478 // Functions returning different error types using thiserror-derived types
479 .register_global_function("safe_parse", |s: &str| -> Result<i64, ValidationError> {
480 s.parse::<i64>()
481 .map_err(|e| ValidationError::ParseError(e.to_string()))
482 })?
483 .register_global_function(
484 "safe_divide",
485 |a: i64, b: i64| -> Result<f64, ValidationError> {
486 if b == 0 {
487 Err(ValidationError::DivisionByZero)
488 } else {
489 Ok(a as f64 / b as f64)
490 }
491 },
492 )?
493 // Additional validation functions with ValidationError
494 .register_global_function(
495 "validate_email",
496 |email: &str| -> Result<bool, ValidationError> {
497 if email.is_empty() {
498 return Err(ValidationError::EmailError(
499 "Email cannot be empty".to_string(),
500 ));
501 }
502 if !email.contains('@') {
503 return Err(ValidationError::EmailError(
504 "Email must contain @ symbol".to_string(),
505 ));
506 }
507 if !email.contains('.') {
508 return Err(ValidationError::EmailError(
509 "Email must contain . symbol".to_string(),
510 ));
511 }
512 let parts: Vec<&str> = email.split('@').collect();
513 if parts.len() != 2 {
514 return Err(ValidationError::EmailError(
515 "Email must contain exactly one @ symbol".to_string(),
516 ));
517 }
518 if parts[0].is_empty() || parts[1].is_empty() {
519 return Err(ValidationError::EmailError(
520 "Email local and domain parts cannot be empty".to_string(),
521 ));
522 }
523 Ok(true)
524 },
525 )?
526 .register_global_function(
527 "validate_age",
528 |age: i64| -> Result<String, ValidationError> {
529 match age {
530 0..=17 => Ok("minor".to_string()),
531 18..=64 => Ok("adult".to_string()),
532 65..=120 => Ok("senior".to_string()),
533 _ => Err(ValidationError::AgeError(format!("Invalid age: {}", age))),
534 }
535 },
536 )?
537 .register_global_function(
538 "validate_range",
539 |x: i64, min: i64, max: i64| -> Result<i64, ValidationError> {
540 if x < min || x > max {
541 Err(ValidationError::RangeError { value: x, min, max })
542 } else {
543 Ok(x)
544 }
545 },
546 )?
547 .build()?;
548
549 let test_cases = vec![
550 // Success cases
551 (
552 "safe_parse('42')",
553 "42",
554 2i64,
555 "valid@email.com",
556 true,
557 "Parse valid number",
558 ),
559 (
560 "safe_divide(10, divisor)",
561 "10",
562 2i64,
563 "test@example.com",
564 true,
565 "Safe division",
566 ),
567 (
568 "validate_email(email)",
569 "25",
570 1i64,
571 "user@domain.com",
572 true,
573 "Valid email",
574 ),
575 (
576 "validate_age(safe_parse(input))",
577 "25",
578 1i64,
579 "user@test.com",
580 true,
581 "Valid age",
582 ),
583 // Error cases
584 (
585 "safe_parse('invalid')",
586 "invalid",
587 2i64,
588 "test@test.com",
589 false,
590 "Parse invalid number",
591 ),
592 (
593 "safe_divide(10, divisor)",
594 "10",
595 0i64,
596 "test@test.com",
597 false,
598 "Division by zero",
599 ),
600 (
601 "validate_email(email)",
602 "10",
603 1i64,
604 "invalid-email",
605 false,
606 "Invalid email format",
607 ),
608 (
609 "validate_age(safe_parse(input))",
610 "150",
611 1i64,
612 "test@test.com",
613 false,
614 "Invalid age",
615 ),
616 (
617 "validate_range(safe_parse(input), 1, 100)",
618 "150",
619 2i64,
620 "test@test.com",
621 false,
622 "Value out of range",
623 ),
624 ];
625
626 for (expr, input, divisor, email, should_succeed, description) in test_cases {
627 let program = env.compile(expr)?;
628 let activation = Activation::new()
629 .bind_variable("input", input)?
630 .bind_variable("divisor", divisor)?
631 .bind_variable("email", email)?;
632
633 match program.evaluate(&activation) {
634 Ok(result) => {
635 println!(" {} = {} ✅ ({})", expr, result, description);
636 if !should_succeed {
637 println!(" ⚠️ Expected this to fail!");
638 }
639 }
640 Err(e) => {
641 println!(" {} -> ERROR: {} ❌ ({})", expr, e, description);
642 if should_succeed {
643 println!(" ⚠️ Expected this to succeed!");
644 }
645 }
646 }
647 }
648
649 println!();
650 Ok(())
651}
652
653/// Demo 7: Program introspection and return types
654fn demo7_program_introspection() -> Result<(), Error> {
655 println!("📌 Demo 7: Program Introspection & Return Types");
656
657 // Test with numeric expression
658 {
659 let env = Env::builder()
660 .declare_variable::<i64>("a")?
661 .declare_variable::<i64>("b")?
662 .build()?;
663 let program = env.compile("a + b")?;
664 let return_type = program.return_type();
665 println!(" Expression: 'a + b'");
666 println!(" Return type: {}", return_type);
667
668 let activation = Activation::new()
669 .bind_variable("a", 10)?
670 .bind_variable("b", 20)?;
671 let result = program.evaluate(&activation)?;
672 println!(" Result: {}", result);
673 }
674
675 // Test with string expression
676 {
677 let env = Env::builder()
678 .declare_variable::<String>("a")?
679 .declare_variable::<String>("b")?
680 .build()?;
681 let program = env.compile("a + b")?;
682 let return_type = program.return_type();
683 println!(" Expression: 'a + b' (strings)");
684 println!(" Return type: {}", return_type);
685
686 let activation = Activation::new()
687 .bind_variable("a", "Hello ".to_string())?
688 .bind_variable("b", "World!".to_string())?;
689 let result = program.evaluate(&activation)?;
690 println!(" Result: {}", result);
691 }
692
693 // Test with boolean expression
694 {
695 let env = Env::builder().declare_variable::<i64>("age")?.build()?;
696 let program = env.compile("age >= 18")?;
697 let return_type = program.return_type();
698 println!(" Expression: 'age >= 18'");
699 println!(" Return type: {}", return_type);
700
701 let activation = Activation::new().bind_variable("age", 25i64)?;
702 let result = program.evaluate(&activation)?;
703 println!(" Result: {}", result);
704 }
705
706 println!();
707 Ok(())
708}
709
710/// Demo 8: Advanced container operations and reference handling
711fn demo8_container_operations() -> Result<(), Error> {
712 println!("📌 Demo 8: Container Operations & Reference Handling");
713
714 let env = Env::builder()
715 .declare_variable::<Vec<&str>>("string_refs")?
716 .declare_variable::<HashMap<i64, &str>>("lookup_table")?
717 .declare_variable::<Option<&str>>("maybe_value")?
718 .declare_variable::<Vec<User>>("users")?
719 // Functions working with reference types
720 .register_global_function("longest_string", |strings: Vec<&str>| -> Option<String> {
721 strings
722 .iter()
723 .max_by_key(|s| s.len())
724 .map(|s| s.to_string())
725 })?
726 .register_global_function(
727 "lookup",
728 |table: HashMap<i64, &str>, key: i64| -> Option<String> {
729 table.get(&key).map(|s| s.to_string())
730 },
731 )?
732 .register_global_function("string_length", |s: Option<&str>| -> i64 {
733 s.map(|s| s.len() as i64).unwrap_or(0)
734 })?
735 // Advanced filtering and mapping
736 .register_global_function("filter_adults", |users: Vec<User>| -> Vec<User> {
737 users.into_iter().filter(|u| u.age >= 18).collect()
738 })?
739 .register_global_function(
740 "group_by_age_range",
741 |users: Vec<User>| -> HashMap<String, Vec<String>> {
742 let mut groups: HashMap<String, Vec<String>> = HashMap::new();
743 for user in users {
744 let range = match user.age {
745 0..=17 => "minor",
746 18..=64 => "adult",
747 _ => "senior",
748 };
749 groups.entry(range.to_string()).or_default().push(user.name);
750 }
751 groups
752 },
753 )?
754 .build()?;
755
756 // Prepare test data with proper lifetimes
757 let source_strings = [
758 "hello".to_string(),
759 "world".to_string(),
760 "rust".to_string(),
761 "programming".to_string(),
762 ];
763 let string_refs: Vec<&str> = source_strings.iter().map(|s| s.as_str()).collect();
764
765 let lookup_table: HashMap<i64, &str> = HashMap::from([(1, "one"), (2, "two"), (3, "three")]);
766
767 let maybe_value: Option<&str> = Some("test string");
768
769 let users = vec![
770 User {
771 id: 1,
772 name: "Alice".to_string(),
773 email: "alice@test.com".to_string(),
774 age: 25,
775 roles: vec![],
776 metadata: HashMap::new(),
777 },
778 User {
779 id: 2,
780 name: "Bob".to_string(),
781 email: "bob@test.com".to_string(),
782 age: 16,
783 roles: vec![],
784 metadata: HashMap::new(),
785 },
786 User {
787 id: 3,
788 name: "Carol".to_string(),
789 email: "carol@test.com".to_string(),
790 age: 67,
791 roles: vec![],
792 metadata: HashMap::new(),
793 },
794 ];
795
796 let test_expressions = vec![
797 ("longest_string(string_refs)", "Find longest string"),
798 ("lookup(lookup_table, 2)", "Lookup value by key"),
799 ("string_length(maybe_value)", "Get optional string length"),
800 ("filter_adults(users).size()", "Count adult users"),
801 (
802 "group_by_age_range(users).size()",
803 "Group users by age range",
804 ),
805 ];
806
807 let activation = Activation::new()
808 .bind_variable("string_refs", string_refs)?
809 .bind_variable("lookup_table", lookup_table)?
810 .bind_variable("maybe_value", maybe_value)?
811 .bind_variable("users", users)?;
812
813 for (expr, description) in test_expressions {
814 let program = env.compile(expr)?;
815 let result = program.evaluate(&activation)?;
816 println!(" {} = {} ({})", expr, result, description);
817 }
818
819 println!();
820 Ok(())
821}
Sourcepub fn declare_function<D>(
self,
name: impl Into<String>,
member: bool,
) -> Result<Self, Error>where
D: FunctionDecl,
pub fn declare_function<D>(
self,
name: impl Into<String>,
member: bool,
) -> Result<Self, Error>where
D: FunctionDecl,
Declares a function signature without providing an implementation.
This is useful when you want to declare that a function exists for type checking purposes, but will provide the implementation later via activation bindings.
§Arguments
name
- The name of the functionmember
- Whether this is a member function (true
) or global function (false
)
§Type Parameters
D
- The function declaration type that specifies the signature
Sourcepub fn declare_member_function<D>(
self,
name: impl Into<String>,
) -> Result<Self, Error>where
D: FunctionDecl,
pub fn declare_member_function<D>(
self,
name: impl Into<String>,
) -> Result<Self, Error>where
D: FunctionDecl,
Sourcepub fn declare_global_function<D>(
self,
name: impl Into<String>,
) -> Result<Self, Error>where
D: FunctionDecl,
pub fn declare_global_function<D>(
self,
name: impl Into<String>,
) -> Result<Self, Error>where
D: FunctionDecl,
Declares a global function signature without providing an implementation.
§Arguments
name
- The name of the global function
§Type Parameters
D
- The function declaration type that specifies the signature
Examples found in repository?
215fn demo2_variable_operations() -> Result<(), Error> {
216 println!("📌 Demo 2: Variable Binding & Providers");
217
218 let env = Env::builder()
219 .declare_variable::<i64>("a")?
220 .declare_variable::<i64>("b")?
221 .declare_global_function::<fn() -> i64>("get_const")?
222 .register_global_function("multiply", |x: i64, y: i64| x * y)?
223 .build()?;
224
225 // Test direct variable binding
226 {
227 println!(" Variable binding:");
228 let program = env.compile("a + b")?;
229 let activation = Activation::new()
230 .bind_variable("a", 10)?
231 .bind_variable("b", 20)?;
232 let result = program.evaluate(&activation)?;
233 println!(" a + b = {} (direct binding)", result);
234 }
235
236 // Test variable provider binding
237 {
238 println!(" Variable provider binding:");
239 let program = env.compile("a * b")?;
240 let activation = Activation::new()
241 .bind_variable("a", 5)?
242 .bind_variable_provider("b", || -> Result<i64, Error> {
243 println!(" Provider called for variable 'b'");
244 Ok(7)
245 })?;
246 let result = program.evaluate(&activation)?;
247 println!(" a * b = {} (provider binding)", result);
248 }
249
250 // Test function declaration and binding
251 {
252 println!(" Function declaration & binding:");
253 let program = env.compile("get_const() + multiply(a, 3)")?;
254 let activation = Activation::new()
255 .bind_variable("a", 4)?
256 .bind_global_function("get_const", || -> Result<i64, Error> { Ok(100) })?;
257 let result = program.evaluate(&activation)?;
258 println!(
259 " get_const() + multiply(a, 3) = {} (function binding)",
260 result
261 );
262 }
263
264 println!();
265 Ok(())
266}
Sourcepub fn define_constant<T>(
self,
name: impl Into<String>,
value: T,
) -> Result<Self, Error>where
T: IntoConstant,
pub fn define_constant<T>(
self,
name: impl Into<String>,
value: T,
) -> Result<Self, Error>where
T: IntoConstant,
Sourcepub fn declare_variable<T>(self, name: impl Into<String>) -> Result<Self, Error>where
T: TypedValue,
pub fn declare_variable<T>(self, name: impl Into<String>) -> Result<Self, Error>where
T: TypedValue,
Declares a variable of a specific type.
This declares that a variable of the given name and type may be provided during evaluation. The actual value must be bound in the activation when evaluating expressions.
§Arguments
name
- The name of the variable
§Type Parameters
T
- The type of the variable
§Examples
use cel_cxx::*;
let builder = Env::builder()
.declare_variable::<String>("user_name")?
.declare_variable::<i64>("age")?;
Examples found in repository?
7fn main() -> Result<(), Error> {
8 println!("🚀 CEL-CXX Basic Example\n");
9
10 // Create an environment with variables and functions
11 let env = Env::builder()
12 .declare_variable::<String>("name")?
13 .declare_variable::<i64>("age")?
14 .register_global_function("greet", |name: &str| format!("Hello, {}!", name))?
15 .register_global_function("is_adult", |age: i64| age >= 18)?
16 .build()?;
17
18 // Compile and evaluate expressions
19 let expressions = vec![
20 "greet(name)",
21 "is_adult(age)",
22 "'Name: ' + name + ', Age: ' + string(age)",
23 "age >= 18 ? 'adult' : 'minor'",
24 ];
25
26 let activation = Activation::new()
27 .bind_variable("name", "Alice")?
28 .bind_variable("age", 25i64)?;
29
30 for expr in expressions {
31 let program = env.compile(expr)?;
32 let result = program.evaluate(&activation)?;
33 println!("{} = {}", expr, result);
34 }
35
36 println!("\n✅ Basic example completed!");
37 Ok(())
38}
More examples
146fn demo1_basic_operations() -> Result<(), Error> {
147 println!("📌 Demo 1: Basic Expressions & Zero-Annotation Functions");
148
149 let env = Env::builder()
150 .declare_variable::<String>("name")?
151 .declare_variable::<i64>("age")?
152 .declare_variable::<f64>("score")?
153 // ✨ Zero-annotation functions - types automatically inferred!
154 .register_global_function("greet", |name: &str| format!("Hello, {}!", name))?
155 .register_global_function("is_adult", |age: i64| age >= 18)?
156 .register_global_function("grade", |score: f64| -> String {
157 match score {
158 90.0..=100.0 => "A".to_string(),
159 80.0..=89.9 => "B".to_string(),
160 70.0..=79.9 => "C".to_string(),
161 60.0..=69.9 => "D".to_string(),
162 _ => "F".to_string(),
163 }
164 })?
165 .register_global_function("calculate_discount", |age: i64, score: f64| -> f64 {
166 let base_discount = if age >= 65 { 0.2 } else { 0.0 };
167 let score_bonus = if score >= 90.0 { 0.1 } else { 0.0 };
168 base_discount + score_bonus
169 })?
170 .build()?;
171
172 // Test basic expressions including simple arithmetic
173 let test_cases = vec![
174 ("1 + 1", "Alice", 25i64, 95.0, "Simple arithmetic"),
175 (
176 "greet(name)",
177 "Alice",
178 25i64,
179 95.0,
180 "Function with string parameter",
181 ),
182 ("is_adult(age)", "Bob", 16i64, 85.0, "Boolean function"),
183 (
184 "grade(score)",
185 "Charlie",
186 30i64,
187 78.5,
188 "String return function",
189 ),
190 (
191 "calculate_discount(age, score)",
192 "Diana",
193 67i64,
194 92.0,
195 "Multi-parameter function",
196 ),
197 ];
198
199 for (expr, name, age, score, description) in test_cases {
200 let program = env.compile(expr)?;
201 let activation = Activation::new()
202 .bind_variable("name", name)?
203 .bind_variable("age", age)?
204 .bind_variable("score", score)?;
205
206 let result = program.evaluate(&activation)?;
207 println!(" {} = {} ({})", expr, result, description);
208 }
209
210 println!();
211 Ok(())
212}
213
214/// Demo 2: Variable binding and providers
215fn demo2_variable_operations() -> Result<(), Error> {
216 println!("📌 Demo 2: Variable Binding & Providers");
217
218 let env = Env::builder()
219 .declare_variable::<i64>("a")?
220 .declare_variable::<i64>("b")?
221 .declare_global_function::<fn() -> i64>("get_const")?
222 .register_global_function("multiply", |x: i64, y: i64| x * y)?
223 .build()?;
224
225 // Test direct variable binding
226 {
227 println!(" Variable binding:");
228 let program = env.compile("a + b")?;
229 let activation = Activation::new()
230 .bind_variable("a", 10)?
231 .bind_variable("b", 20)?;
232 let result = program.evaluate(&activation)?;
233 println!(" a + b = {} (direct binding)", result);
234 }
235
236 // Test variable provider binding
237 {
238 println!(" Variable provider binding:");
239 let program = env.compile("a * b")?;
240 let activation = Activation::new()
241 .bind_variable("a", 5)?
242 .bind_variable_provider("b", || -> Result<i64, Error> {
243 println!(" Provider called for variable 'b'");
244 Ok(7)
245 })?;
246 let result = program.evaluate(&activation)?;
247 println!(" a * b = {} (provider binding)", result);
248 }
249
250 // Test function declaration and binding
251 {
252 println!(" Function declaration & binding:");
253 let program = env.compile("get_const() + multiply(a, 3)")?;
254 let activation = Activation::new()
255 .bind_variable("a", 4)?
256 .bind_global_function("get_const", || -> Result<i64, Error> { Ok(100) })?;
257 let result = program.evaluate(&activation)?;
258 println!(
259 " get_const() + multiply(a, 3) = {} (function binding)",
260 result
261 );
262 }
263
264 println!();
265 Ok(())
266}
267
268/// Demo 3: Opaque types with member functions
269fn demo3_opaque_member_functions() -> Result<(), Error> {
270 println!("📌 Demo 3: Opaque Types & Member Functions");
271
272 let env = Env::builder()
273 .declare_variable::<Student>("student")?
274 // ✨ Register struct methods directly using RustType::method_name syntax
275 .register_member_function("get_name", Student::get_name)?
276 .register_member_function("get_age", Student::get_age)?
277 .register_member_function("get_grade", Student::get_grade)?
278 .register_member_function("is_passing", Student::is_passing)?
279 .register_member_function("has_subject", Student::has_subject)?
280 .register_member_function("get_letter_grade", Student::get_letter_grade)?
281 .build()?;
282
283 let student = Student {
284 name: "John Doe".to_string(),
285 age: 18,
286 grade: 87.5,
287 subjects: vec!["Math".to_string(), "Physics".to_string()],
288 };
289
290 let activation = Activation::new().bind_variable("student", student)?;
291
292 let test_expressions = vec![
293 ("student.get_name()", "Get student name"),
294 ("student.get_age()", "Get student age"),
295 ("student.get_grade()", "Get numerical grade"),
296 ("student.get_letter_grade()", "Get letter grade"),
297 ("student.is_passing()", "Check if passing"),
298 ("student.has_subject('Math')", "Check if has Math subject"),
299 (
300 "student.has_subject('Chemistry')",
301 "Check if has Chemistry subject",
302 ),
303 ];
304
305 for (expr, description) in test_expressions {
306 let program = env.compile(expr)?;
307 let result = program.evaluate(&activation)?;
308 println!(" {} = {} ({})", expr, result, description);
309 }
310
311 println!();
312 Ok(())
313}
314
315/// Demo 4: Type conversions and standard Rust types
316fn demo4_type_conversions() -> Result<(), Error> {
317 println!("📌 Demo 4: Type Conversions & Standard Rust Types");
318
319 // Functions returning different types (both direct values and Results)
320 fn return_string_direct() -> String {
321 "hello world".to_string()
322 }
323 fn return_int_result() -> Result<i64, std::io::Error> {
324 Ok(42)
325 }
326 fn return_list() -> Vec<i64> {
327 vec![1, 2, 3, 4, 5]
328 }
329 fn return_map() -> HashMap<String, i64> {
330 let mut map = HashMap::new();
331 map.insert("key1".to_string(), 100);
332 map.insert("key2".to_string(), 200);
333 map
334 }
335 fn return_optional_some() -> Option<String> {
336 Some("optional value".to_string())
337 }
338 fn return_optional_none() -> Option<String> {
339 None
340 }
341
342 let env = Env::builder()
343 .register_global_function("return_string_direct", return_string_direct)?
344 .register_global_function("return_int_result", return_int_result)?
345 .register_global_function("return_list", return_list)?
346 .register_global_function("return_map", return_map)?
347 .register_global_function("return_optional_some", return_optional_some)?
348 .register_global_function("return_optional_none", return_optional_none)?
349 .build()?;
350
351 let test_cases = vec![
352 ("return_string_direct()", "String conversion"),
353 ("return_int_result()", "Result<i64> conversion"),
354 ("return_list()", "Vec<i64> conversion"),
355 ("return_map()", "HashMap conversion"),
356 ("return_optional_some()", "Option<String> Some conversion"),
357 ("return_optional_none()", "Option<String> None conversion"),
358 ];
359
360 for (expr, description) in test_cases {
361 let program = env.compile(expr)?;
362 let result = program.evaluate(())?;
363 println!(" {} = {} ({})", expr, result, description);
364
365 // Demonstrate type conversion back to Rust types
366 match expr {
367 "return_string_direct()" => {
368 let rust_string: String = result
369 .try_into()
370 .map_err(|_| Error::invalid_argument("string conversion failed".to_string()))?;
371 println!(" Converted back to Rust String: '{}'", rust_string);
372 }
373 "return_int_result()" => {
374 let rust_int: i64 = result
375 .try_into()
376 .map_err(|_| Error::invalid_argument("int conversion failed".to_string()))?;
377 println!(" Converted back to Rust i64: {}", rust_int);
378 }
379 "return_list()" => {
380 let rust_list: Vec<i64> = result
381 .try_into()
382 .map_err(|_| Error::invalid_argument("list conversion failed".to_string()))?;
383 println!(" Converted back to Rust Vec<i64>: {:?}", rust_list);
384 }
385 _ => {}
386 }
387 }
388
389 println!();
390 Ok(())
391}
392
393/// Demo 5: Generic functions and type annotations
394fn demo5_generic_functions() -> Result<(), Error> {
395 println!("📌 Demo 5: Generic Functions & Function Overloads");
396
397 // Generic function that counts items in any Vec<T>
398 fn count_items<T>(items: Vec<T>) -> i64 {
399 items.len() as i64
400 }
401
402 // Generic function that processes maps
403 fn get_map_size<K, V>(map: HashMap<K, V>) -> i64 {
404 map.len() as i64
405 }
406
407 // Function working with references in containers
408 fn join_strings(strings: Vec<&str>, separator: &str) -> String {
409 strings.join(separator)
410 }
411
412 let env = Env::builder()
413 .declare_variable::<Vec<String>>("string_list")?
414 .declare_variable::<Vec<i64>>("int_list")?
415 .declare_variable::<HashMap<String, i64>>("score_map")?
416 .declare_variable::<Vec<f64>>("floats")?
417 // Register generic functions with specific type annotations
418 .register_global_function("count_strings", count_items::<String>)?
419 .register_global_function("count_ints", count_items::<i64>)?
420 .register_global_function("get_string_map_size", get_map_size::<String, i64>)?
421 .register_global_function("join_strings", join_strings)?
422 // Multiple functions with same name, different signatures (overloads)
423 .register_global_function("process", |x: i64| x * 2)?
424 .register_global_function("process", |x: f64| (x * 2.0).round())?
425 .register_global_function("process", |x: String| x.to_uppercase())?
426 // Overloaded member functions for different container types
427 .register_member_function("sum", |numbers: Vec<i64>| numbers.iter().sum::<i64>())?
428 .register_member_function("sum", |floats: Vec<f64>| floats.iter().sum::<f64>())?
429 .build()?;
430
431 let test_cases = vec![
432 // Generic function tests
433 ("count_strings(string_list)", "Count strings in list"),
434 ("count_ints(int_list)", "Count integers in list"),
435 ("get_string_map_size(score_map)", "Get map size"),
436 ("join_strings(string_list, ' ')", "Join strings with space"),
437 // Function overload tests
438 ("process(42)", "Process integer (multiply by 2)"),
439 ("process(3.14)", "Process float (multiply by 2, round)"),
440 ("process('hello')", "Process string (uppercase)"),
441 ("int_list.sum()", "Sum integers"),
442 ("floats.sum()", "Sum floats"),
443 ];
444
445 let activation = Activation::new()
446 .bind_variable(
447 "string_list",
448 vec!["hello".to_string(), "world".to_string(), "rust".to_string()],
449 )?
450 .bind_variable("int_list", vec![1, 2, 3, 4, 5, 6])?
451 .bind_variable("floats", vec![1.5, 2.7, 3.143, 4.0])?
452 .bind_variable("score_map", {
453 let mut map = HashMap::new();
454 map.insert("alice".to_string(), 95);
455 map.insert("bob".to_string(), 87);
456 map.insert("charlie".to_string(), 92);
457 map
458 })?;
459
460 for (expr, description) in test_cases {
461 let program = env.compile(expr)?;
462 let result = program.evaluate(&activation)?;
463 println!(" {} = {} ({})", expr, result, description);
464 }
465
466 println!();
467 Ok(())
468}
469
470/// Demo 6: Error handling with different error types including Box<dyn std::error::Error>
471fn demo6_error_handling() -> Result<(), Error> {
472 println!("📌 Demo 6: Error Handling & Validation");
473
474 let env = Env::builder()
475 .declare_variable::<String>("input")?
476 .declare_variable::<i64>("divisor")?
477 .declare_variable::<String>("email")?
478 // Functions returning different error types using thiserror-derived types
479 .register_global_function("safe_parse", |s: &str| -> Result<i64, ValidationError> {
480 s.parse::<i64>()
481 .map_err(|e| ValidationError::ParseError(e.to_string()))
482 })?
483 .register_global_function(
484 "safe_divide",
485 |a: i64, b: i64| -> Result<f64, ValidationError> {
486 if b == 0 {
487 Err(ValidationError::DivisionByZero)
488 } else {
489 Ok(a as f64 / b as f64)
490 }
491 },
492 )?
493 // Additional validation functions with ValidationError
494 .register_global_function(
495 "validate_email",
496 |email: &str| -> Result<bool, ValidationError> {
497 if email.is_empty() {
498 return Err(ValidationError::EmailError(
499 "Email cannot be empty".to_string(),
500 ));
501 }
502 if !email.contains('@') {
503 return Err(ValidationError::EmailError(
504 "Email must contain @ symbol".to_string(),
505 ));
506 }
507 if !email.contains('.') {
508 return Err(ValidationError::EmailError(
509 "Email must contain . symbol".to_string(),
510 ));
511 }
512 let parts: Vec<&str> = email.split('@').collect();
513 if parts.len() != 2 {
514 return Err(ValidationError::EmailError(
515 "Email must contain exactly one @ symbol".to_string(),
516 ));
517 }
518 if parts[0].is_empty() || parts[1].is_empty() {
519 return Err(ValidationError::EmailError(
520 "Email local and domain parts cannot be empty".to_string(),
521 ));
522 }
523 Ok(true)
524 },
525 )?
526 .register_global_function(
527 "validate_age",
528 |age: i64| -> Result<String, ValidationError> {
529 match age {
530 0..=17 => Ok("minor".to_string()),
531 18..=64 => Ok("adult".to_string()),
532 65..=120 => Ok("senior".to_string()),
533 _ => Err(ValidationError::AgeError(format!("Invalid age: {}", age))),
534 }
535 },
536 )?
537 .register_global_function(
538 "validate_range",
539 |x: i64, min: i64, max: i64| -> Result<i64, ValidationError> {
540 if x < min || x > max {
541 Err(ValidationError::RangeError { value: x, min, max })
542 } else {
543 Ok(x)
544 }
545 },
546 )?
547 .build()?;
548
549 let test_cases = vec![
550 // Success cases
551 (
552 "safe_parse('42')",
553 "42",
554 2i64,
555 "valid@email.com",
556 true,
557 "Parse valid number",
558 ),
559 (
560 "safe_divide(10, divisor)",
561 "10",
562 2i64,
563 "test@example.com",
564 true,
565 "Safe division",
566 ),
567 (
568 "validate_email(email)",
569 "25",
570 1i64,
571 "user@domain.com",
572 true,
573 "Valid email",
574 ),
575 (
576 "validate_age(safe_parse(input))",
577 "25",
578 1i64,
579 "user@test.com",
580 true,
581 "Valid age",
582 ),
583 // Error cases
584 (
585 "safe_parse('invalid')",
586 "invalid",
587 2i64,
588 "test@test.com",
589 false,
590 "Parse invalid number",
591 ),
592 (
593 "safe_divide(10, divisor)",
594 "10",
595 0i64,
596 "test@test.com",
597 false,
598 "Division by zero",
599 ),
600 (
601 "validate_email(email)",
602 "10",
603 1i64,
604 "invalid-email",
605 false,
606 "Invalid email format",
607 ),
608 (
609 "validate_age(safe_parse(input))",
610 "150",
611 1i64,
612 "test@test.com",
613 false,
614 "Invalid age",
615 ),
616 (
617 "validate_range(safe_parse(input), 1, 100)",
618 "150",
619 2i64,
620 "test@test.com",
621 false,
622 "Value out of range",
623 ),
624 ];
625
626 for (expr, input, divisor, email, should_succeed, description) in test_cases {
627 let program = env.compile(expr)?;
628 let activation = Activation::new()
629 .bind_variable("input", input)?
630 .bind_variable("divisor", divisor)?
631 .bind_variable("email", email)?;
632
633 match program.evaluate(&activation) {
634 Ok(result) => {
635 println!(" {} = {} ✅ ({})", expr, result, description);
636 if !should_succeed {
637 println!(" ⚠️ Expected this to fail!");
638 }
639 }
640 Err(e) => {
641 println!(" {} -> ERROR: {} ❌ ({})", expr, e, description);
642 if should_succeed {
643 println!(" ⚠️ Expected this to succeed!");
644 }
645 }
646 }
647 }
648
649 println!();
650 Ok(())
651}
652
653/// Demo 7: Program introspection and return types
654fn demo7_program_introspection() -> Result<(), Error> {
655 println!("📌 Demo 7: Program Introspection & Return Types");
656
657 // Test with numeric expression
658 {
659 let env = Env::builder()
660 .declare_variable::<i64>("a")?
661 .declare_variable::<i64>("b")?
662 .build()?;
663 let program = env.compile("a + b")?;
664 let return_type = program.return_type();
665 println!(" Expression: 'a + b'");
666 println!(" Return type: {}", return_type);
667
668 let activation = Activation::new()
669 .bind_variable("a", 10)?
670 .bind_variable("b", 20)?;
671 let result = program.evaluate(&activation)?;
672 println!(" Result: {}", result);
673 }
674
675 // Test with string expression
676 {
677 let env = Env::builder()
678 .declare_variable::<String>("a")?
679 .declare_variable::<String>("b")?
680 .build()?;
681 let program = env.compile("a + b")?;
682 let return_type = program.return_type();
683 println!(" Expression: 'a + b' (strings)");
684 println!(" Return type: {}", return_type);
685
686 let activation = Activation::new()
687 .bind_variable("a", "Hello ".to_string())?
688 .bind_variable("b", "World!".to_string())?;
689 let result = program.evaluate(&activation)?;
690 println!(" Result: {}", result);
691 }
692
693 // Test with boolean expression
694 {
695 let env = Env::builder().declare_variable::<i64>("age")?.build()?;
696 let program = env.compile("age >= 18")?;
697 let return_type = program.return_type();
698 println!(" Expression: 'age >= 18'");
699 println!(" Return type: {}", return_type);
700
701 let activation = Activation::new().bind_variable("age", 25i64)?;
702 let result = program.evaluate(&activation)?;
703 println!(" Result: {}", result);
704 }
705
706 println!();
707 Ok(())
708}
709
710/// Demo 8: Advanced container operations and reference handling
711fn demo8_container_operations() -> Result<(), Error> {
712 println!("📌 Demo 8: Container Operations & Reference Handling");
713
714 let env = Env::builder()
715 .declare_variable::<Vec<&str>>("string_refs")?
716 .declare_variable::<HashMap<i64, &str>>("lookup_table")?
717 .declare_variable::<Option<&str>>("maybe_value")?
718 .declare_variable::<Vec<User>>("users")?
719 // Functions working with reference types
720 .register_global_function("longest_string", |strings: Vec<&str>| -> Option<String> {
721 strings
722 .iter()
723 .max_by_key(|s| s.len())
724 .map(|s| s.to_string())
725 })?
726 .register_global_function(
727 "lookup",
728 |table: HashMap<i64, &str>, key: i64| -> Option<String> {
729 table.get(&key).map(|s| s.to_string())
730 },
731 )?
732 .register_global_function("string_length", |s: Option<&str>| -> i64 {
733 s.map(|s| s.len() as i64).unwrap_or(0)
734 })?
735 // Advanced filtering and mapping
736 .register_global_function("filter_adults", |users: Vec<User>| -> Vec<User> {
737 users.into_iter().filter(|u| u.age >= 18).collect()
738 })?
739 .register_global_function(
740 "group_by_age_range",
741 |users: Vec<User>| -> HashMap<String, Vec<String>> {
742 let mut groups: HashMap<String, Vec<String>> = HashMap::new();
743 for user in users {
744 let range = match user.age {
745 0..=17 => "minor",
746 18..=64 => "adult",
747 _ => "senior",
748 };
749 groups.entry(range.to_string()).or_default().push(user.name);
750 }
751 groups
752 },
753 )?
754 .build()?;
755
756 // Prepare test data with proper lifetimes
757 let source_strings = [
758 "hello".to_string(),
759 "world".to_string(),
760 "rust".to_string(),
761 "programming".to_string(),
762 ];
763 let string_refs: Vec<&str> = source_strings.iter().map(|s| s.as_str()).collect();
764
765 let lookup_table: HashMap<i64, &str> = HashMap::from([(1, "one"), (2, "two"), (3, "three")]);
766
767 let maybe_value: Option<&str> = Some("test string");
768
769 let users = vec![
770 User {
771 id: 1,
772 name: "Alice".to_string(),
773 email: "alice@test.com".to_string(),
774 age: 25,
775 roles: vec![],
776 metadata: HashMap::new(),
777 },
778 User {
779 id: 2,
780 name: "Bob".to_string(),
781 email: "bob@test.com".to_string(),
782 age: 16,
783 roles: vec![],
784 metadata: HashMap::new(),
785 },
786 User {
787 id: 3,
788 name: "Carol".to_string(),
789 email: "carol@test.com".to_string(),
790 age: 67,
791 roles: vec![],
792 metadata: HashMap::new(),
793 },
794 ];
795
796 let test_expressions = vec![
797 ("longest_string(string_refs)", "Find longest string"),
798 ("lookup(lookup_table, 2)", "Lookup value by key"),
799 ("string_length(maybe_value)", "Get optional string length"),
800 ("filter_adults(users).size()", "Count adult users"),
801 (
802 "group_by_age_range(users).size()",
803 "Group users by age range",
804 ),
805 ];
806
807 let activation = Activation::new()
808 .bind_variable("string_refs", string_refs)?
809 .bind_variable("lookup_table", lookup_table)?
810 .bind_variable("maybe_value", maybe_value)?
811 .bind_variable("users", users)?;
812
813 for (expr, description) in test_expressions {
814 let program = env.compile(expr)?;
815 let result = program.evaluate(&activation)?;
816 println!(" {} = {} ({})", expr, result, description);
817 }
818
819 println!();
820 Ok(())
821}
Sourcepub fn build(self) -> Result<Env<'f, Fm, Rm>, Error>
pub fn build(self) -> Result<Env<'f, Fm, Rm>, Error>
Builds the environment from the configured builder.
This method consumes the builder and creates the final Env
instance
that can be used to compile CEL expressions.
§Returns
Returns a Result
containing the built Env
or an Error
if
the environment could not be created.
§Examples
use cel_cxx::*;
let env = Env::builder()
.declare_variable::<String>("name")?
.build()?;
§Errors
Returns an error if the environment configuration is invalid or if the underlying CEL environment cannot be created.
Examples found in repository?
7fn main() -> Result<(), Error> {
8 println!("🚀 CEL-CXX Basic Example\n");
9
10 // Create an environment with variables and functions
11 let env = Env::builder()
12 .declare_variable::<String>("name")?
13 .declare_variable::<i64>("age")?
14 .register_global_function("greet", |name: &str| format!("Hello, {}!", name))?
15 .register_global_function("is_adult", |age: i64| age >= 18)?
16 .build()?;
17
18 // Compile and evaluate expressions
19 let expressions = vec![
20 "greet(name)",
21 "is_adult(age)",
22 "'Name: ' + name + ', Age: ' + string(age)",
23 "age >= 18 ? 'adult' : 'minor'",
24 ];
25
26 let activation = Activation::new()
27 .bind_variable("name", "Alice")?
28 .bind_variable("age", 25i64)?;
29
30 for expr in expressions {
31 let program = env.compile(expr)?;
32 let result = program.evaluate(&activation)?;
33 println!("{} = {}", expr, result);
34 }
35
36 println!("\n✅ Basic example completed!");
37 Ok(())
38}
More examples
146fn demo1_basic_operations() -> Result<(), Error> {
147 println!("📌 Demo 1: Basic Expressions & Zero-Annotation Functions");
148
149 let env = Env::builder()
150 .declare_variable::<String>("name")?
151 .declare_variable::<i64>("age")?
152 .declare_variable::<f64>("score")?
153 // ✨ Zero-annotation functions - types automatically inferred!
154 .register_global_function("greet", |name: &str| format!("Hello, {}!", name))?
155 .register_global_function("is_adult", |age: i64| age >= 18)?
156 .register_global_function("grade", |score: f64| -> String {
157 match score {
158 90.0..=100.0 => "A".to_string(),
159 80.0..=89.9 => "B".to_string(),
160 70.0..=79.9 => "C".to_string(),
161 60.0..=69.9 => "D".to_string(),
162 _ => "F".to_string(),
163 }
164 })?
165 .register_global_function("calculate_discount", |age: i64, score: f64| -> f64 {
166 let base_discount = if age >= 65 { 0.2 } else { 0.0 };
167 let score_bonus = if score >= 90.0 { 0.1 } else { 0.0 };
168 base_discount + score_bonus
169 })?
170 .build()?;
171
172 // Test basic expressions including simple arithmetic
173 let test_cases = vec![
174 ("1 + 1", "Alice", 25i64, 95.0, "Simple arithmetic"),
175 (
176 "greet(name)",
177 "Alice",
178 25i64,
179 95.0,
180 "Function with string parameter",
181 ),
182 ("is_adult(age)", "Bob", 16i64, 85.0, "Boolean function"),
183 (
184 "grade(score)",
185 "Charlie",
186 30i64,
187 78.5,
188 "String return function",
189 ),
190 (
191 "calculate_discount(age, score)",
192 "Diana",
193 67i64,
194 92.0,
195 "Multi-parameter function",
196 ),
197 ];
198
199 for (expr, name, age, score, description) in test_cases {
200 let program = env.compile(expr)?;
201 let activation = Activation::new()
202 .bind_variable("name", name)?
203 .bind_variable("age", age)?
204 .bind_variable("score", score)?;
205
206 let result = program.evaluate(&activation)?;
207 println!(" {} = {} ({})", expr, result, description);
208 }
209
210 println!();
211 Ok(())
212}
213
214/// Demo 2: Variable binding and providers
215fn demo2_variable_operations() -> Result<(), Error> {
216 println!("📌 Demo 2: Variable Binding & Providers");
217
218 let env = Env::builder()
219 .declare_variable::<i64>("a")?
220 .declare_variable::<i64>("b")?
221 .declare_global_function::<fn() -> i64>("get_const")?
222 .register_global_function("multiply", |x: i64, y: i64| x * y)?
223 .build()?;
224
225 // Test direct variable binding
226 {
227 println!(" Variable binding:");
228 let program = env.compile("a + b")?;
229 let activation = Activation::new()
230 .bind_variable("a", 10)?
231 .bind_variable("b", 20)?;
232 let result = program.evaluate(&activation)?;
233 println!(" a + b = {} (direct binding)", result);
234 }
235
236 // Test variable provider binding
237 {
238 println!(" Variable provider binding:");
239 let program = env.compile("a * b")?;
240 let activation = Activation::new()
241 .bind_variable("a", 5)?
242 .bind_variable_provider("b", || -> Result<i64, Error> {
243 println!(" Provider called for variable 'b'");
244 Ok(7)
245 })?;
246 let result = program.evaluate(&activation)?;
247 println!(" a * b = {} (provider binding)", result);
248 }
249
250 // Test function declaration and binding
251 {
252 println!(" Function declaration & binding:");
253 let program = env.compile("get_const() + multiply(a, 3)")?;
254 let activation = Activation::new()
255 .bind_variable("a", 4)?
256 .bind_global_function("get_const", || -> Result<i64, Error> { Ok(100) })?;
257 let result = program.evaluate(&activation)?;
258 println!(
259 " get_const() + multiply(a, 3) = {} (function binding)",
260 result
261 );
262 }
263
264 println!();
265 Ok(())
266}
267
268/// Demo 3: Opaque types with member functions
269fn demo3_opaque_member_functions() -> Result<(), Error> {
270 println!("📌 Demo 3: Opaque Types & Member Functions");
271
272 let env = Env::builder()
273 .declare_variable::<Student>("student")?
274 // ✨ Register struct methods directly using RustType::method_name syntax
275 .register_member_function("get_name", Student::get_name)?
276 .register_member_function("get_age", Student::get_age)?
277 .register_member_function("get_grade", Student::get_grade)?
278 .register_member_function("is_passing", Student::is_passing)?
279 .register_member_function("has_subject", Student::has_subject)?
280 .register_member_function("get_letter_grade", Student::get_letter_grade)?
281 .build()?;
282
283 let student = Student {
284 name: "John Doe".to_string(),
285 age: 18,
286 grade: 87.5,
287 subjects: vec!["Math".to_string(), "Physics".to_string()],
288 };
289
290 let activation = Activation::new().bind_variable("student", student)?;
291
292 let test_expressions = vec![
293 ("student.get_name()", "Get student name"),
294 ("student.get_age()", "Get student age"),
295 ("student.get_grade()", "Get numerical grade"),
296 ("student.get_letter_grade()", "Get letter grade"),
297 ("student.is_passing()", "Check if passing"),
298 ("student.has_subject('Math')", "Check if has Math subject"),
299 (
300 "student.has_subject('Chemistry')",
301 "Check if has Chemistry subject",
302 ),
303 ];
304
305 for (expr, description) in test_expressions {
306 let program = env.compile(expr)?;
307 let result = program.evaluate(&activation)?;
308 println!(" {} = {} ({})", expr, result, description);
309 }
310
311 println!();
312 Ok(())
313}
314
315/// Demo 4: Type conversions and standard Rust types
316fn demo4_type_conversions() -> Result<(), Error> {
317 println!("📌 Demo 4: Type Conversions & Standard Rust Types");
318
319 // Functions returning different types (both direct values and Results)
320 fn return_string_direct() -> String {
321 "hello world".to_string()
322 }
323 fn return_int_result() -> Result<i64, std::io::Error> {
324 Ok(42)
325 }
326 fn return_list() -> Vec<i64> {
327 vec![1, 2, 3, 4, 5]
328 }
329 fn return_map() -> HashMap<String, i64> {
330 let mut map = HashMap::new();
331 map.insert("key1".to_string(), 100);
332 map.insert("key2".to_string(), 200);
333 map
334 }
335 fn return_optional_some() -> Option<String> {
336 Some("optional value".to_string())
337 }
338 fn return_optional_none() -> Option<String> {
339 None
340 }
341
342 let env = Env::builder()
343 .register_global_function("return_string_direct", return_string_direct)?
344 .register_global_function("return_int_result", return_int_result)?
345 .register_global_function("return_list", return_list)?
346 .register_global_function("return_map", return_map)?
347 .register_global_function("return_optional_some", return_optional_some)?
348 .register_global_function("return_optional_none", return_optional_none)?
349 .build()?;
350
351 let test_cases = vec![
352 ("return_string_direct()", "String conversion"),
353 ("return_int_result()", "Result<i64> conversion"),
354 ("return_list()", "Vec<i64> conversion"),
355 ("return_map()", "HashMap conversion"),
356 ("return_optional_some()", "Option<String> Some conversion"),
357 ("return_optional_none()", "Option<String> None conversion"),
358 ];
359
360 for (expr, description) in test_cases {
361 let program = env.compile(expr)?;
362 let result = program.evaluate(())?;
363 println!(" {} = {} ({})", expr, result, description);
364
365 // Demonstrate type conversion back to Rust types
366 match expr {
367 "return_string_direct()" => {
368 let rust_string: String = result
369 .try_into()
370 .map_err(|_| Error::invalid_argument("string conversion failed".to_string()))?;
371 println!(" Converted back to Rust String: '{}'", rust_string);
372 }
373 "return_int_result()" => {
374 let rust_int: i64 = result
375 .try_into()
376 .map_err(|_| Error::invalid_argument("int conversion failed".to_string()))?;
377 println!(" Converted back to Rust i64: {}", rust_int);
378 }
379 "return_list()" => {
380 let rust_list: Vec<i64> = result
381 .try_into()
382 .map_err(|_| Error::invalid_argument("list conversion failed".to_string()))?;
383 println!(" Converted back to Rust Vec<i64>: {:?}", rust_list);
384 }
385 _ => {}
386 }
387 }
388
389 println!();
390 Ok(())
391}
392
393/// Demo 5: Generic functions and type annotations
394fn demo5_generic_functions() -> Result<(), Error> {
395 println!("📌 Demo 5: Generic Functions & Function Overloads");
396
397 // Generic function that counts items in any Vec<T>
398 fn count_items<T>(items: Vec<T>) -> i64 {
399 items.len() as i64
400 }
401
402 // Generic function that processes maps
403 fn get_map_size<K, V>(map: HashMap<K, V>) -> i64 {
404 map.len() as i64
405 }
406
407 // Function working with references in containers
408 fn join_strings(strings: Vec<&str>, separator: &str) -> String {
409 strings.join(separator)
410 }
411
412 let env = Env::builder()
413 .declare_variable::<Vec<String>>("string_list")?
414 .declare_variable::<Vec<i64>>("int_list")?
415 .declare_variable::<HashMap<String, i64>>("score_map")?
416 .declare_variable::<Vec<f64>>("floats")?
417 // Register generic functions with specific type annotations
418 .register_global_function("count_strings", count_items::<String>)?
419 .register_global_function("count_ints", count_items::<i64>)?
420 .register_global_function("get_string_map_size", get_map_size::<String, i64>)?
421 .register_global_function("join_strings", join_strings)?
422 // Multiple functions with same name, different signatures (overloads)
423 .register_global_function("process", |x: i64| x * 2)?
424 .register_global_function("process", |x: f64| (x * 2.0).round())?
425 .register_global_function("process", |x: String| x.to_uppercase())?
426 // Overloaded member functions for different container types
427 .register_member_function("sum", |numbers: Vec<i64>| numbers.iter().sum::<i64>())?
428 .register_member_function("sum", |floats: Vec<f64>| floats.iter().sum::<f64>())?
429 .build()?;
430
431 let test_cases = vec![
432 // Generic function tests
433 ("count_strings(string_list)", "Count strings in list"),
434 ("count_ints(int_list)", "Count integers in list"),
435 ("get_string_map_size(score_map)", "Get map size"),
436 ("join_strings(string_list, ' ')", "Join strings with space"),
437 // Function overload tests
438 ("process(42)", "Process integer (multiply by 2)"),
439 ("process(3.14)", "Process float (multiply by 2, round)"),
440 ("process('hello')", "Process string (uppercase)"),
441 ("int_list.sum()", "Sum integers"),
442 ("floats.sum()", "Sum floats"),
443 ];
444
445 let activation = Activation::new()
446 .bind_variable(
447 "string_list",
448 vec!["hello".to_string(), "world".to_string(), "rust".to_string()],
449 )?
450 .bind_variable("int_list", vec![1, 2, 3, 4, 5, 6])?
451 .bind_variable("floats", vec![1.5, 2.7, 3.143, 4.0])?
452 .bind_variable("score_map", {
453 let mut map = HashMap::new();
454 map.insert("alice".to_string(), 95);
455 map.insert("bob".to_string(), 87);
456 map.insert("charlie".to_string(), 92);
457 map
458 })?;
459
460 for (expr, description) in test_cases {
461 let program = env.compile(expr)?;
462 let result = program.evaluate(&activation)?;
463 println!(" {} = {} ({})", expr, result, description);
464 }
465
466 println!();
467 Ok(())
468}
469
470/// Demo 6: Error handling with different error types including Box<dyn std::error::Error>
471fn demo6_error_handling() -> Result<(), Error> {
472 println!("📌 Demo 6: Error Handling & Validation");
473
474 let env = Env::builder()
475 .declare_variable::<String>("input")?
476 .declare_variable::<i64>("divisor")?
477 .declare_variable::<String>("email")?
478 // Functions returning different error types using thiserror-derived types
479 .register_global_function("safe_parse", |s: &str| -> Result<i64, ValidationError> {
480 s.parse::<i64>()
481 .map_err(|e| ValidationError::ParseError(e.to_string()))
482 })?
483 .register_global_function(
484 "safe_divide",
485 |a: i64, b: i64| -> Result<f64, ValidationError> {
486 if b == 0 {
487 Err(ValidationError::DivisionByZero)
488 } else {
489 Ok(a as f64 / b as f64)
490 }
491 },
492 )?
493 // Additional validation functions with ValidationError
494 .register_global_function(
495 "validate_email",
496 |email: &str| -> Result<bool, ValidationError> {
497 if email.is_empty() {
498 return Err(ValidationError::EmailError(
499 "Email cannot be empty".to_string(),
500 ));
501 }
502 if !email.contains('@') {
503 return Err(ValidationError::EmailError(
504 "Email must contain @ symbol".to_string(),
505 ));
506 }
507 if !email.contains('.') {
508 return Err(ValidationError::EmailError(
509 "Email must contain . symbol".to_string(),
510 ));
511 }
512 let parts: Vec<&str> = email.split('@').collect();
513 if parts.len() != 2 {
514 return Err(ValidationError::EmailError(
515 "Email must contain exactly one @ symbol".to_string(),
516 ));
517 }
518 if parts[0].is_empty() || parts[1].is_empty() {
519 return Err(ValidationError::EmailError(
520 "Email local and domain parts cannot be empty".to_string(),
521 ));
522 }
523 Ok(true)
524 },
525 )?
526 .register_global_function(
527 "validate_age",
528 |age: i64| -> Result<String, ValidationError> {
529 match age {
530 0..=17 => Ok("minor".to_string()),
531 18..=64 => Ok("adult".to_string()),
532 65..=120 => Ok("senior".to_string()),
533 _ => Err(ValidationError::AgeError(format!("Invalid age: {}", age))),
534 }
535 },
536 )?
537 .register_global_function(
538 "validate_range",
539 |x: i64, min: i64, max: i64| -> Result<i64, ValidationError> {
540 if x < min || x > max {
541 Err(ValidationError::RangeError { value: x, min, max })
542 } else {
543 Ok(x)
544 }
545 },
546 )?
547 .build()?;
548
549 let test_cases = vec![
550 // Success cases
551 (
552 "safe_parse('42')",
553 "42",
554 2i64,
555 "valid@email.com",
556 true,
557 "Parse valid number",
558 ),
559 (
560 "safe_divide(10, divisor)",
561 "10",
562 2i64,
563 "test@example.com",
564 true,
565 "Safe division",
566 ),
567 (
568 "validate_email(email)",
569 "25",
570 1i64,
571 "user@domain.com",
572 true,
573 "Valid email",
574 ),
575 (
576 "validate_age(safe_parse(input))",
577 "25",
578 1i64,
579 "user@test.com",
580 true,
581 "Valid age",
582 ),
583 // Error cases
584 (
585 "safe_parse('invalid')",
586 "invalid",
587 2i64,
588 "test@test.com",
589 false,
590 "Parse invalid number",
591 ),
592 (
593 "safe_divide(10, divisor)",
594 "10",
595 0i64,
596 "test@test.com",
597 false,
598 "Division by zero",
599 ),
600 (
601 "validate_email(email)",
602 "10",
603 1i64,
604 "invalid-email",
605 false,
606 "Invalid email format",
607 ),
608 (
609 "validate_age(safe_parse(input))",
610 "150",
611 1i64,
612 "test@test.com",
613 false,
614 "Invalid age",
615 ),
616 (
617 "validate_range(safe_parse(input), 1, 100)",
618 "150",
619 2i64,
620 "test@test.com",
621 false,
622 "Value out of range",
623 ),
624 ];
625
626 for (expr, input, divisor, email, should_succeed, description) in test_cases {
627 let program = env.compile(expr)?;
628 let activation = Activation::new()
629 .bind_variable("input", input)?
630 .bind_variable("divisor", divisor)?
631 .bind_variable("email", email)?;
632
633 match program.evaluate(&activation) {
634 Ok(result) => {
635 println!(" {} = {} ✅ ({})", expr, result, description);
636 if !should_succeed {
637 println!(" ⚠️ Expected this to fail!");
638 }
639 }
640 Err(e) => {
641 println!(" {} -> ERROR: {} ❌ ({})", expr, e, description);
642 if should_succeed {
643 println!(" ⚠️ Expected this to succeed!");
644 }
645 }
646 }
647 }
648
649 println!();
650 Ok(())
651}
652
653/// Demo 7: Program introspection and return types
654fn demo7_program_introspection() -> Result<(), Error> {
655 println!("📌 Demo 7: Program Introspection & Return Types");
656
657 // Test with numeric expression
658 {
659 let env = Env::builder()
660 .declare_variable::<i64>("a")?
661 .declare_variable::<i64>("b")?
662 .build()?;
663 let program = env.compile("a + b")?;
664 let return_type = program.return_type();
665 println!(" Expression: 'a + b'");
666 println!(" Return type: {}", return_type);
667
668 let activation = Activation::new()
669 .bind_variable("a", 10)?
670 .bind_variable("b", 20)?;
671 let result = program.evaluate(&activation)?;
672 println!(" Result: {}", result);
673 }
674
675 // Test with string expression
676 {
677 let env = Env::builder()
678 .declare_variable::<String>("a")?
679 .declare_variable::<String>("b")?
680 .build()?;
681 let program = env.compile("a + b")?;
682 let return_type = program.return_type();
683 println!(" Expression: 'a + b' (strings)");
684 println!(" Return type: {}", return_type);
685
686 let activation = Activation::new()
687 .bind_variable("a", "Hello ".to_string())?
688 .bind_variable("b", "World!".to_string())?;
689 let result = program.evaluate(&activation)?;
690 println!(" Result: {}", result);
691 }
692
693 // Test with boolean expression
694 {
695 let env = Env::builder().declare_variable::<i64>("age")?.build()?;
696 let program = env.compile("age >= 18")?;
697 let return_type = program.return_type();
698 println!(" Expression: 'age >= 18'");
699 println!(" Return type: {}", return_type);
700
701 let activation = Activation::new().bind_variable("age", 25i64)?;
702 let result = program.evaluate(&activation)?;
703 println!(" Result: {}", result);
704 }
705
706 println!();
707 Ok(())
708}
709
710/// Demo 8: Advanced container operations and reference handling
711fn demo8_container_operations() -> Result<(), Error> {
712 println!("📌 Demo 8: Container Operations & Reference Handling");
713
714 let env = Env::builder()
715 .declare_variable::<Vec<&str>>("string_refs")?
716 .declare_variable::<HashMap<i64, &str>>("lookup_table")?
717 .declare_variable::<Option<&str>>("maybe_value")?
718 .declare_variable::<Vec<User>>("users")?
719 // Functions working with reference types
720 .register_global_function("longest_string", |strings: Vec<&str>| -> Option<String> {
721 strings
722 .iter()
723 .max_by_key(|s| s.len())
724 .map(|s| s.to_string())
725 })?
726 .register_global_function(
727 "lookup",
728 |table: HashMap<i64, &str>, key: i64| -> Option<String> {
729 table.get(&key).map(|s| s.to_string())
730 },
731 )?
732 .register_global_function("string_length", |s: Option<&str>| -> i64 {
733 s.map(|s| s.len() as i64).unwrap_or(0)
734 })?
735 // Advanced filtering and mapping
736 .register_global_function("filter_adults", |users: Vec<User>| -> Vec<User> {
737 users.into_iter().filter(|u| u.age >= 18).collect()
738 })?
739 .register_global_function(
740 "group_by_age_range",
741 |users: Vec<User>| -> HashMap<String, Vec<String>> {
742 let mut groups: HashMap<String, Vec<String>> = HashMap::new();
743 for user in users {
744 let range = match user.age {
745 0..=17 => "minor",
746 18..=64 => "adult",
747 _ => "senior",
748 };
749 groups.entry(range.to_string()).or_default().push(user.name);
750 }
751 groups
752 },
753 )?
754 .build()?;
755
756 // Prepare test data with proper lifetimes
757 let source_strings = [
758 "hello".to_string(),
759 "world".to_string(),
760 "rust".to_string(),
761 "programming".to_string(),
762 ];
763 let string_refs: Vec<&str> = source_strings.iter().map(|s| s.as_str()).collect();
764
765 let lookup_table: HashMap<i64, &str> = HashMap::from([(1, "one"), (2, "two"), (3, "three")]);
766
767 let maybe_value: Option<&str> = Some("test string");
768
769 let users = vec![
770 User {
771 id: 1,
772 name: "Alice".to_string(),
773 email: "alice@test.com".to_string(),
774 age: 25,
775 roles: vec![],
776 metadata: HashMap::new(),
777 },
778 User {
779 id: 2,
780 name: "Bob".to_string(),
781 email: "bob@test.com".to_string(),
782 age: 16,
783 roles: vec![],
784 metadata: HashMap::new(),
785 },
786 User {
787 id: 3,
788 name: "Carol".to_string(),
789 email: "carol@test.com".to_string(),
790 age: 67,
791 roles: vec![],
792 metadata: HashMap::new(),
793 },
794 ];
795
796 let test_expressions = vec![
797 ("longest_string(string_refs)", "Find longest string"),
798 ("lookup(lookup_table, 2)", "Lookup value by key"),
799 ("string_length(maybe_value)", "Get optional string length"),
800 ("filter_adults(users).size()", "Count adult users"),
801 (
802 "group_by_age_range(users).size()",
803 "Group users by age range",
804 ),
805 ];
806
807 let activation = Activation::new()
808 .bind_variable("string_refs", string_refs)?
809 .bind_variable("lookup_table", lookup_table)?
810 .bind_variable("maybe_value", maybe_value)?
811 .bind_variable("users", users)?;
812
813 for (expr, description) in test_expressions {
814 let program = env.compile(expr)?;
815 let result = program.evaluate(&activation)?;
816 println!(" {} = {} ({})", expr, result, description);
817 }
818
819 println!();
820 Ok(())
821}
Source§impl<'f, Rm: RuntimeMarker> EnvBuilder<'f, (), Rm>
impl<'f, Rm: RuntimeMarker> EnvBuilder<'f, (), Rm>
Sourcepub fn force_async(self) -> EnvBuilder<'f, Async, Rm>
Available on crate feature async
only.
pub fn force_async(self) -> EnvBuilder<'f, Async, Rm>
async
only.Forces conversion to an async environment builder.
This method converts a synchronous environment builder to an asynchronous one, allowing it to register async functions and build async environments.
§Examples
use cel_cxx::*;
let async_builder = Env::builder().force_async();
Source§impl<'f, Fm: FnMarker> EnvBuilder<'f, Fm, ()>
impl<'f, Fm: FnMarker> EnvBuilder<'f, Fm, ()>
Sourcepub fn use_runtime<Rt: Runtime>(self) -> EnvBuilder<'f, Fm, Rt>
Available on crate feature async
only.
pub fn use_runtime<Rt: Runtime>(self) -> EnvBuilder<'f, Fm, Rt>
async
only.Sourcepub fn use_tokio(self) -> EnvBuilder<'f, Fm, Tokio>
Available on crate features async
and tokio
only.
pub fn use_tokio(self) -> EnvBuilder<'f, Fm, Tokio>
async
and tokio
only.Configures the builder to use the Tokio async runtime.
This is a convenience method for setting the runtime to Tokio.
Requires the tokio
feature to be enabled.
§Examples
use cel_cxx::*;
let builder = Env::builder().use_tokio();
Sourcepub fn use_async_std(self) -> EnvBuilder<'f, Fm, AsyncStd>
Available on crate features async
and async-std
only.
pub fn use_async_std(self) -> EnvBuilder<'f, Fm, AsyncStd>
async
and async-std
only.Configures the builder to use the async-std runtime.
This is a convenience method for setting the runtime to async-std.
Requires the async-std
feature to be enabled.
§Examples
use cel_cxx::*;
let builder = Env::builder().use_async_std();
Sourcepub fn use_smol(self) -> EnvBuilder<'f, Fm, Smol>
Available on crate features async
and smol
only.
pub fn use_smol(self) -> EnvBuilder<'f, Fm, Smol>
async
and smol
only.Configures the builder to use the smol runtime.
This is a convenience method for setting the runtime to smol.
Requires the smol
feature to be enabled.
§Examples
use cel_cxx::*;
let builder = Env::builder().use_smol();
Trait Implementations§
Source§impl<'f, Fm: FnMarker, Rm: RuntimeMarker> Debug for EnvBuilder<'f, Fm, Rm>
impl<'f, Fm: FnMarker, Rm: RuntimeMarker> Debug for EnvBuilder<'f, Fm, Rm>
Source§impl<'f, Fm: FnMarker, Rm: RuntimeMarker> Default for EnvBuilder<'f, Fm, Rm>
impl<'f, Fm: FnMarker, Rm: RuntimeMarker> Default for EnvBuilder<'f, Fm, Rm>
Auto Trait Implementations§
impl<'f, Fm, Rm> Freeze for EnvBuilder<'f, Fm, Rm>
impl<'f, Fm = (), Rm = ()> !RefUnwindSafe for EnvBuilder<'f, Fm, Rm>
impl<'f, Fm, Rm> Send for EnvBuilder<'f, Fm, Rm>
impl<'f, Fm, Rm> Sync for EnvBuilder<'f, Fm, Rm>
impl<'f, Fm, Rm> Unpin for EnvBuilder<'f, Fm, Rm>
impl<'f, Fm = (), Rm = ()> !UnwindSafe for EnvBuilder<'f, Fm, Rm>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left
is true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left(&self)
returns true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read more