camel-dsl
DSL support for rust-camel (YAML, etc)
Overview
camel-dsl provides Domain Specific Language support for defining routes in rust-camel declaratively. Routes can be defined in YAML files and loaded at runtime, enabling external configuration without recompiling the application.
This crate is useful when you want to:
- Externalize route configuration
- Define routes without writing Rust code
- Enable non-developers to modify routes via configuration files
Features
-
YAML route definitions: Define routes using YAML syntax
-
Declarative integration flows: Use all available EIPs and DSL
-
External configuration: Load routes from files at runtime
-
Language expressions: Use
simple:andrhai:syntax for dynamic values -
Route-level configuration: Auto-startup, startup ordering, concurrency, error handling, circuit breaker, unit-of-work hooks
-
Environment variable interpolation: Inject env vars in route files using
${env:VAR_NAME}syntax -
All step types: to, log, set_header, set_body, transform, filter, choice, split, aggregate, wire_tap, multicast, stop, script, bean
Installation
Add to your Cargo.toml:
[]
= "0.2"
Quick Start
Basic Route
Create a YAML file routes.yaml:
routes:
- id: "hello-timer"
from: "timer:tick?period=2000"
steps:
- log: "Timer fired!"
- to: "log:info"
Load and add to context:
use load_from_file;
use CamelContext;
use TimerComponent;
use LogComponent;
let mut ctx = new;
ctx.register_component;
ctx.register_component;
let routes = load_from_file?;
for route in routes
ctx.start.await?;
With Language Expressions
routes:
- id: "filter-demo"
from: "timer:tick?period=1000"
steps:
- set_header:
key: "type"
value: "allowed"
- filter:
simple: "${header.type} == 'allowed'"
steps:
- log: "Passed filter!"
- to: "log:filtered"
Available Step Types
| Step | Description | Example |
|---|---|---|
to |
Send to endpoint | - to: "log:info" |
log |
Log message | - log: "Processing" |
set_header |
Set header | - set_header: { key: "x", value: "y" } |
set_body |
Set body | - set_body: { value: "content" } |
transform |
Transform body (alias for set_body) |
- transform: { simple: "${body}" } |
marshal |
Serialize body using a data format (e.g., Json → Text) | - marshal: json |
unmarshal |
Deserialize body using a data format (e.g., Text → Json) | - unmarshal: xml |
filter |
Filter messages | - filter: { simple: "${header.type} == 'allowed'", steps: [...] } |
choice |
Content-based router | - choice: { when: [...], otherwise: [...] } |
split |
Split message | - split: { expression: "body_lines", steps: [...] } |
aggregate |
Aggregate messages | - aggregate: { header: "id", completion_size: 5 } |
wire_tap |
Fire-and-forget tap | - wire_tap: "direct:audit" |
multicast |
Fan-out to multiple | - multicast: { steps: [...] } |
stop |
Stop pipeline | - stop: true |
script |
Execute script | - script: { language: "simple", source: "${body}" } |
bean |
Invoke bean method | - bean: { name: "orderService", method: "process" } |
Marshal/Unmarshal Example
routes:
- id: "convert-format"
from: "direct:input"
steps:
- unmarshal: json
- marshal: xml
- to: "direct:output"
This converts the message body from JSON to XML using the built-in data formats.
Bean Step
The bean step allows you to invoke business logic registered in the BeanRegistry:
routes:
- id: "process-order"
from: "direct:orders"
steps:
- bean:
name: "orderService"
method: "validate"
- bean:
name: "orderService"
method: "process"
Prerequisites:
- Register beans in your Rust code using
BeanRegistry - Pass the registry to
DefaultRouteController::with_beans()
use BeanRegistry;
use DefaultRouteController;
let mut bean_registry = new;
bean_registry.register;
let controller = with_beans;
See examples/bean-demo for a complete example.
Environment Variable Interpolation
Use ${env:VAR_NAME} anywhere in a YAML route file to inject an environment variable at load time:
routes:
- id: "env-demo"
from: "timer:tick?period=1000"
steps:
- log: "Broker: ${env:BROKER_URL}"
- to: "${env:OUTPUT_ENDPOINT}"
The substitution happens before YAML parsing, so it works in any position — URIs, log messages, header values, etc. Unset variables are left as-is (the literal ${env:VAR_NAME} string).
Language Expressions
Many steps support language expressions for dynamic values: predicates:
Syntax
# Simple language shortcut
- filter:
simple: "${header.type} == 'allowed'"
steps:
- log: "Match!"
# Explicit language + source
- set_header:
key: "computed"
language: "simple"
source: "${header.base} + '-suffix'"
# Rhai script
- script:
language: "rhai"
source: |
let body = exchange.body();
body.to_upper()
Available Languages
simple- Built-in Simple language (supports header/body interpolation)rhai- Rhai scripting language (requirescamel-language-rhaifeature)
Route-Level Configuration
routes:
- id: "my-route"
from: "timer:tick?period=1000"
auto_startup: true # Default: true
startup_order: 100 # Default: 1000, lower = earlier
concurrency: concurrent # or "sequential"
error_handler: # Optional error handling
dead_letter_channel: "log:errors"
# Legacy single catch-all retry (still supported)
retry:
max_attempts: 3
initial_delay_ms: 100
# New ordered exception clauses (first-match-wins)
on_exceptions:
- kind: "Io"
retry:
max_attempts: 3
initial_delay_ms: 100
handled_by: "log:io-errors"
- kind: "ProcessorError"
message_contains: "validation"
retry:
max_attempts: 1
circuit_breaker: # Optional circuit breaker
failure_threshold: 5
open_duration_ms: 30000
on_complete: "direct:on-complete" # Optional completion hook URI
on_failure: "direct:on-failure" # Optional failure hook URI
Unit of Work Hooks (YAML)
routes:
- id: my-route
from: "timer:tick"
on_complete: "log:done"
on_failure: "log:failed"
steps:
- log: "processing"