Keyboard shortcuts

Press โ† or โ†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Simple

A lightweight expression and predicate language for header, body, property, and exception access. It uses ${...} interpolation syntax and supports compound predicates with && and ||.


    let lang = SimpleLanguage::new();

    let order_pred = lang
        .create_predicate("${header.type} == 'order'")
        .expect("valid predicate"); // allow-unwrap

    let high_priority_order_pred = lang
        .create_predicate("${header.type} == 'order' && ${header.priority} == 'high'")
        .expect("valid compound predicate"); // allow-unwrap

    let body_present_pred = lang
        .create_predicate("${body} != null")
        .expect("valid body presence predicate"); // allow-unwrap

    let approved_pred = lang
        .create_predicate("${header.approved} == true")
        .expect("valid boolean predicate"); // allow-unwrap

    let type_expr = lang
        .create_expression("${header.type}")
        .expect("valid expression"); // allow-unwrap

    let counter = Arc::new(AtomicU64::new(0));
    let counter_clone = Arc::clone(&counter);

    let types = ["order", "invoice", "order", "shipment"];
    let priorities = ["high", "low", "high", "low"];

    let handle = tokio::runtime::Handle::current();

    let route = RouteBuilder::from("timer:tick?period=800&repeatCount=8")
        .route_id("language-simple-demo")
        // Step 1: assign rotating headers and alternate empty/non-empty 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 msg_type = types[n % types.len()];
                let priority = priorities[n % priorities.len()];
                let approved = n.is_multiple_of(2);
                exchange
                    .input
                    .set_header("type", camel_api::Value::String(msg_type.to_string()));
                exchange
                    .input
                    .set_header("priority", camel_api::Value::String(priority.to_string()));
                exchange
                    .input
                    .set_header("approved", camel_api::Value::Bool(approved));

                exchange.input.body = if n.is_multiple_of(3) {
                    Body::Empty
                } else {
                    Body::Text(format!("message #{}", n + 1))
                };
                Ok(exchange)
            })
        })
        // Step 2: demonstrate body null check
        .filter({
            let pred = Arc::clone(&body_present_pred);
            let h = handle.clone();
            move |ex: &camel_api::Exchange| h.block_on(pred.matches(ex)).unwrap_or(false)
        })
        .to("log:body-present?showBody=true&showHeaders=true")
        .end_filter()
        // Step 3: use Simple Language expression to append type info to body
        .process({
            let type_expr = Arc::clone(&type_expr);
            move |mut exchange: camel_api::Exchange| {
                let expr = Arc::clone(&type_expr);
                Box::pin(async move {
                    if let Ok(camel_api::Value::String(t)) = expr.evaluate(&exchange).await {
                        let current = exchange.input.body.as_text().unwrap_or("").to_string();
                        exchange.input.body = Body::Text(format!("{current} [type={t}]"));
                    }
                    Ok(exchange)
                })
            }
        })
        // Step 4: filter โ€” only 'order' messages pass
        .filter({
            let pred = Arc::clone(&order_pred);
            let h = handle.clone();
            move |ex: &camel_api::Exchange| h.block_on(pred.matches(ex)).unwrap_or(false)
        })
        .to("log:orders?showBody=true&showHeaders=true")
        .end_filter()
        // Step 5: compound predicate with && for high-priority orders
        .filter({
            let pred = Arc::clone(&high_priority_order_pred);
            let h = handle.clone();
            move |ex: &camel_api::Exchange| h.block_on(pred.matches(ex)).unwrap_or(false)
        })
        .to("log:high-priority-orders?showBody=true&showHeaders=true")
        .end_filter()
        // Step 6: boolean comparison against true literal
        .filter({
            let pred = Arc::clone(&approved_pred);
            let h = handle.clone();
            move |ex: &camel_api::Exchange| h.block_on(pred.matches(ex)).unwrap_or(false)
        })
        .to("log:approved-orders?showBody=true&showHeaders=true")
        .end_filter()
        .build()?;
YAML equivalent
- id: language-simple-demo
  from: timer:tick?period=800&repeatCount=8
  steps:
    - set_header:
        key: type
        value: order
    - set_header:
        key: priority
        value: high
    - set_header:
        key: approved
        value: "true"
    - set_body:
        simple: "${header.type}"
    - filter:
        simple: "${body} != null"
        steps:
          - to: log:body-present?showBody=true&showHeaders=true
    - filter:
        simple: "${header.type} == 'order'"
        steps:
          - to: log:orders?showBody=true&showHeaders=true
    - filter:
        simple: "${header.type} == 'order' && ${header.priority} == 'high'"
        steps:
          - to: log:high-priority-orders?showBody=true&showHeaders=true
    - filter:
        simple: "${header.approved} == true"
        steps:
          - to: log:approved-orders?showBody=true&showHeaders=true

Simple parses its source once into an AST and reuses it for each Exchange. You build predicates and expressions up front with create_predicate and create_expression, then move them into route closures. Predicates return a boolean for filter and choice steps. Expressions return a Value for enrichment and transformation. The included example constructs four predicates and one expression before the route.

The language evaluates ${header.x}, ${body}, and ${exchangeProperty.y} against the Exchange. Missing headers, properties, and exception messages evaluate to Value::Null. At the predicate boundary, null is false. All other values, including an empty string, are true. Within && and ||, null and empty strings are false. Simple also supports language delegation through ${lang:expr}, which resolves the target language at evaluation time.

Simple is the most common language for predicates in EIP patterns. Use it for flat header and body access in filter, choice, and enrichment steps. It requires no external engine. For structured JSON queries, use JSONPath.

Reference: Language SPI ยท Simple crate