Rhai
A Rhai implementation of the Language SPI. It provides Expression, Predicate, and MutatingExpression with an unconditional in-process sandbox and Rust-native type safety.
let lang = RhaiLanguage::new();
// Expression 1: prepend "[URGENT] " when priority header is "high"
let enrich_expr = lang
.create_expression(
r#"
if header("priority") == "high" {
"[URGENT] " + body
} else {
body
}
"#,
)
.expect("valid expression"); // allow-unwrap
// Expression 2: in-script mutation — compute a tax string using
// set_header to store an intermediate value, then read it back.
let tax_expr = lang
.create_expression(
r#"
let tax = header("amount") * 0.1;
set_header("tax", tax);
"tax=" + header("tax")
"#,
)
.expect("valid expression"); // allow-unwrap
// Predicate: only let through messages where amount > 100
let high_value_pred = lang
.create_predicate(r#"header("amount") > 100"#)
.expect("valid predicate"); // allow-unwrap
let enrich_expr = Arc::new(enrich_expr);
let tax_expr = Arc::new(tax_expr);
let high_value_pred = Arc::new(high_value_pred);
let route = RouteBuilder::from("timer:tick?period=900&repeatCount=6")
.route_id("language-rhai-demo")
// Step 1: assign headers and body
.process(move |mut exchange: camel_api::Exchange| {
let c = Arc::clone(&counter_clone);
Box::pin(async move {
let n = c.fetch_add(1, Ordering::SeqCst) as usize;
let priority = priorities[n % priorities.len()];
let amount = amounts[n % amounts.len()];
exchange
.input
.set_header("priority", camel_api::Value::String(priority.to_string()));
exchange
.input
.set_header("amount", camel_api::Value::from(amount));
exchange.input.body = Body::Text(format!("order #{}", n + 1));
Ok(exchange)
})
})
// Step 2: Rhai expression enriches the body based on priority
.process({
let expr = Arc::clone(&enrich_expr);
move |mut exchange: camel_api::Exchange| {
let expr = Arc::clone(&expr);
Box::pin(async move {
if let Ok(camel_api::Value::String(enriched)) = expr.evaluate(&exchange).await {
exchange.input.body = Body::Text(enriched);
}
Ok(exchange)
})
}
})
.process({
let expr = Arc::clone(&tax_expr);
move |mut exchange: camel_api::Exchange| {
let expr = Arc::clone(&expr);
Box::pin(async move {
if let Ok(camel_api::Value::String(tax_info)) = expr.evaluate(&exchange).await {
let body = exchange.input.body.as_text().unwrap_or("").to_string();
exchange.input.body = Body::Text(format!("{body} ({tax_info})"));
}
Ok(exchange)
})
}
})
// Step 4: .script() — mutating Rhai expression tags the order and
// appends a status suffix. Changes propagate back to the Exchange.
.script(
"rhai",
r#"
headers["processed"] = true;
let status = if headers["priority"] == "high" { "PRIORITY" } else { "STANDARD" };
body = body + " [" + status + "]";
"#,
)
// Step 5: log every message (after enrichment)
.to("log:all-orders?showBody=true&showHeaders=true")
// Step 6: filter — only high-value orders (amount > 100) to alert log
.filter({
let pred = Arc::clone(&high_value_pred);
let handle = tokio::runtime::Handle::current();
move |ex: &camel_api::Exchange| handle.block_on(pred.matches(ex)).unwrap_or(false)
})
.to("log:high-value-alert?showBody=true")
.end_filter()
.build()?;
YAML equivalent
- id: language-rhai-demo
from: timer:tick?period=900&repeatCount=6
steps:
- set_header:
key: priority
value: high
- set_header:
key: amount
value: 200
- set_body:
value: "order #1"
- script:
language: rhai
source: |
headers["processed"] = true;
let status = if headers["priority"] == "high" { "PRIORITY" } else { "STANDARD" };
body = body + " [" + status + "]";
- to: log:all-orders?showBody=true&showHeaders=true
- filter:
rhai: 'header("amount") > 100'
steps:
- to: log:high-value-alert?showBody=true
You register rhai into CamelContext by name, then build expressions and predicates up front. The included example constructs two expressions and one predicate before the route. Read-only expressions and predicates expose body and headers variables plus header(), set_header(), property(), and set_property() host functions. Their writes affect only the current evaluation. A MutatingExpression exposes body, headers, and properties as mutable scope variables. The implementation writes all three back to the Exchange only after successful evaluation.
Rhai integrates with Rust types without a foreign-function boundary. Exchange values bind directly as Rhai values, and the engine has no external runtime dependency. The sandbox closes filesystem, module, and network access through independent layers. The workspace enables Rhai's no_module feature, each evaluation uses Engine::new_raw(), and disable_symbol blocks eval and import. The sandbox has no configuration opt-out. Rhai source is trusted operator configuration. Exchange data is untrusted under ADR-0032 and never evaluated as source code.
Resource limits bound CPU and memory use. Defaults are 100,000 max operations, 1 MiB max string size, 10,000 max array elements, 10,000 max map entries, 64 max expression depth, and a 5,000 ms execution timeout. The timeout wraps synchronous evaluation in spawn_blocking. It returns control to the route after five seconds but cannot cancel the blocking task. The operation limit eventually stops a CPU-bound script.
Use Rhai for complex logic in pipeline steps: branching, computation, and multi-step mutation. For flat header and body access, Simple is lighter and needs no engine. For JavaScript-syntax scripting, use JavaScript.
Reference: Language SPI · Rhai crate