camel-config 0.45.2

Configuration and route discovery for rust-camel
Documentation

camel-config

Configuration and route discovery for rust-camel

Configuration management for the Rust Camel framework. Provides profile-based configuration with environment variable overrides, component defaults, and supervision settings.

Overview

camel-config manages application configuration through:

  • Camel.toml files - Central configuration file
  • Config modularization - Split config across files with include = [...]
  • Profile support - Environment-specific settings ([default], [production], etc.)
  • Environment variables - Override any setting with CAMEL_* variables
  • Route discovery - Automatic route file discovery via glob patterns
  • Component defaults - Global defaults for HTTP, Kafka, JMS, Redis, SQL, File, Container components
  • Supervision configuration - Retry and backoff settings for route supervision

Features

  • 🎯 Profile-based configuration - Deep merge of profile settings with defaults
  • 📦 Config modularization - Split Camel.toml across multiple files with include
  • 🌍 Environment variable overrides - Override any config value with CAMEL_* prefix
  • 📁 Route discovery - Automatic route file discovery from glob patterns
  • ⚙️ Component defaults - Set global defaults for all component endpoints
  • 🔄 Hot reload support - Optional file watching for configuration changes
  • 🔧 Supervision settings - Configure retry strategies and backoff policies

Camel.toml Format

Create a Camel.toml file in your project root:

[default]
routes = ["routes/*.yaml"]    # Default examples use YAML; explicit .json globs are also supported
# Wildcard globs skip *.test.yaml / *.test.yml camel test documents (owned by `camel test`); naming one literally fails with the reserved-suffix error
watch = false
log_level = "info"
drain_timeout_ms = 10000  # Graceful drain timeout for hot-reload restart/remove (default: 10s)
watch_debounce_ms = 300  # Debounce window for hot-reload file watcher (ms)

[default.supervision]
initial_delay_ms = 1000
backoff_multiplier = 2.0
max_delay_ms = 60000
max_attempts = 5

# Component defaults - apply to all endpoints unless overridden by URI
[default.components.http]
connect_timeout_ms = 5000
response_timeout_ms = 30000
max_connections = 100
allow_internal = false

[default.components.kafka]
brokers = "localhost:9092"
group_id = "camel"
session_timeout_ms = 45000

[default.components.redis]
host = "localhost"
port = 6379

[default.components.sql]
max_connections = 5
min_connections = 1
idle_timeout_secs = 300

[default.components.file]
delay_ms = 500
initial_delay_ms = 1000
read_timeout_ms = 30000
write_timeout_ms = 30000

[default.components.jms]
default_broker = "main"

[default.components.jms.brokers.main]
broker_url = "tcp://localhost:61616"
broker_type = "activemq"

[default.components.container]
docker_host = "unix:///var/run/docker.sock"

# Observability
[default.observability.prometheus]
enabled = true
port = 9090

[default.observability.health]
enabled = false    # standalone health server (disabled by default)
port = 8080        # health server port

[production]
log_level = "warn"
watch = false

[production.components.kafka]
brokers = "prod-kafka:9092"
group_id = "camel-prod"

[production.components.redis]
host = "prod-redis"
port = 6379

[development]
log_level = "debug"
watch = true

[development.components.http]
allow_internal = true  # Allow internal services in dev

Configuration Sections

  • [default] - Base configuration (required)
  • [default.supervision] - Default supervision settings
  • [default.components.<name>] - Component-specific global defaults
  • [default.observability.<name>] - Observability settings (prometheus, health, otel, tracer)
  • [<profile>] - Profile-specific overrides (merged with default)
  • [<profile>.supervision] - Profile-specific supervision overrides
  • [<profile>.components.<name>] - Profile-specific component overrides
  • [beans.<name>] - WASM bean plugin registrations

Core Fields

Field Type Default Description
routes [String] Glob patterns for route files (default examples use YAML; explicit .json globs also supported). Wildcard globs skip *.test.yaml / *.test.yml camel test documents, which camel test owns. Naming one literally fails with the reserved-suffix error
watch bool Enable hot reload on file changes
runtime_journal [runtime_journal] table unset Optional redb runtime event journal (path, durability, compaction_threshold_events); when set, enables local runtime journal replay
idempotent_repo [idempotent_repo] table unset Optional persistent redb idempotent repository (path, durability); when set, registers a "redb" backend for idempotent_consumer steps. The default "memory" repo remains available either way
log_level String Logging level (trace/debug/info/warn/error)
timeout_ms u64 Default operation timeout
drain_timeout_ms u64 Max time to wait for in-flight exchanges to complete on Restart/Remove (default: 10000)
watch_debounce_ms u64 300 Debounce window in ms for the hot-reload file watcher. Set to 0 to disable debouncing.
supervision.* - Retry and backoff settings
observability.health.enabled bool false Enable standalone health server
observability.health.port u16 8080 Health server port

[idempotent_repo] — Persistent Idempotent Repository (opt-in)

By default, idempotency is volatile: the built-in "memory" repository loses its keys on restart, so duplicate messages arriving after a restart are reprocessed. Setting [default.idempotent_repo] registers an additional "redb" backend — an embedded, ACID, pure-Rust store (redb) — that persists dedup keys to disk so at-most-once delivery survives restarts. It is local/embedded only; multi-replica topologies still need a shared store (Redis/SQL-backed).

[default.idempotent_repo]
path = ".camel/idempotent.redb"   # Created if missing (parent dir is created too)
durability = "immediate"          # "immediate" (default, fsync per added key) | "eventual" (no fsync)
  • Reference it from a route step: idempotent_consumer { repository: "redb", ... }. The default "memory" repo stays registered, so routes that omit a repository or name "memory" are unaffected.
  • Durability trade-off: immediate (the default) fsyncs on every added key — full at-most-once correctness across OS/power crash, at the cost of one fsync per deduplicated message on the hot path. eventual skips fsync for throughput, accepting that a crash may lose recently-added keys (at-least-once degradation). Prefer eventual only for high-throughput routes that tolerate occasional reprocessing.

Component Defaults

Configure global defaults for each component. URI parameters always take precedence.

HTTP Component

[default.components.http]
connect_timeout_ms = 5000      # Connection timeout (default: 30000)
response_timeout_ms = 30000    # Response timeout (default: none)
max_connections = 100          # Max concurrent connections (default: 100)
max_body_size = 10485760       # Max response body size, 10MB (default: 10MB)
max_request_body = 2097152     # Max request body for server, 2MB (default: 2MB)
allow_internal = false      # Allow requests to private IPs (default: false)

Kafka Component

[default.components.kafka]
brokers = "localhost:9092"     # Bootstrap servers (default: localhost:9092)
group_id = "camel"             # Consumer group ID (default: camel)
session_timeout_ms = 45000     # Consumer session timeout (default: 45000)
request_timeout_ms = 30000     # Producer request timeout (default: 30000)
auto_offset_reset = "latest"   # Offset reset: earliest/latest/none (default: latest)
security_protocol = "plaintext" # Security protocol (default: plaintext)

JMS Component

Requires the jms feature flag:

[dependencies]
camel-config = { version = "*", features = ["jms"] }
[default.components.jms]
default_broker = "main"                       # which broker to use when no ?broker= param
max_bridges = 8                               # max concurrent bridge processes (default: 8)
bridge_cache_dir = "/path/to/cache"           # bridge binary cache directory (default: XDG cache)
bridge_start_timeout_ms = 10000               # bridge startup timeout in ms (default: 10000)
broker_reconnect_interval_ms = 5000           # reconnect interval in ms (default: 5000)

[default.components.jms.brokers.main]
broker_url = "tcp://localhost:61616"          # broker connection URL
broker_type = "activemq"                      # "activemq" or "artemis"
username = "admin"                            # optional broker username
password = "admin"                            # optional broker password

# Add more brokers as needed:
# [default.components.jms.brokers.secondary]
# broker_url = "tcp://other-host:61616"
# broker_type = "artemis"

Redis Component

[default.components.redis]
host = "localhost"             # Redis host (default: localhost)
port = 6379                    # Redis port (default: 6379)

SQL Component

[default.components.sql]
max_connections = 5            # Max pool connections (default: 5)
min_connections = 1            # Min pool connections (default: 1)
idle_timeout_secs = 300        # Idle connection timeout (default: 300)
max_lifetime_secs = 1800       # Max connection lifetime (default: 1800)
ssl_mode = "require"           # SSL mode (optional)
ssl_root_cert = "/etc/ssl/ca.pem"  # CA certificate path (optional)
ssl_cert = "/etc/ssl/client.pem"   # Client certificate path (optional)
ssl_key = "/etc/ssl/client.key"    # Client key path (optional)

File Component

[default.components.file]
delay_ms = 500                 # Poll interval (default: 500)
initial_delay_ms = 1000        # Initial delay before first poll (default: 1000)
read_timeout_ms = 30000        # Read timeout (default: 30000)
write_timeout_ms = 30000       # Write timeout (default: 30000)

Container Component

[default.components.container]
docker_host = "unix:///var/run/docker.sock"  # Docker daemon socket (default)

Beans

[beans.auth]
plugin = "my-auth-bean"

WASM bean plugins registered at startup. Each entry creates an isolated WASM instance.

Optional WASM runtime limits

Each bean can override the WASM runtime defaults via a [limits] sub-table. Fields are Option<T>; any unset field falls back to the runtime default (per ADR-0011 — no silent surprises).

[default.beans.carto-kit]
plugin = "carto-kit"

[default.beans.carto-kit.limits]
timeout-secs = 600          # default: 30
max-memory = 4294967296     # default: 52428800 (50 MiB)
max-concurrent-calls = 4    # default: 4 (informational for beans)

The shared WasmLimitsConfig type lives in crates/camel-config/src/wasm_limits.rs and is re-exported as camel_config::WasmLimitsConfig. The same type is embedded in [security.permissions.providers.<name>] for WASM authorization policies. See ADR-0014 for the design rationale.

Config Modularization (include)

Split your Camel.toml into smaller files using the top-level include field. Included files are merged in order — last wins — before the root file is applied. This is useful for sharing component defaults across deployments while keeping environment-specific overrides in the root file.

# Camel.toml
include = ["config/components.toml", "config/observability.toml"]

[default]
routes = ["routes/*.yaml"]
log_level = "info"

[production]
log_level = "warn"
# config/components.toml  (flat — no profile sections required)
[components.http]
connect_timeout_ms = 5000
max_connections = 200

[components.kafka]
brokers = "kafka:9092"
group_id = "camel"
# config/observability.toml
[observability.prometheus]
enabled = true
port = 9090

Rules:

  • Paths are relative to the file declaring include (no absolute paths, no URL schemes).
  • Path traversal outside the directory is rejected (../ and symlinks that escape).
  • Duplicate paths in the same include list are a fatal error.
  • Included files may not themselves use include (no recursive includes in V1 — a warning is logged and the field is ignored).
  • Profile propagation: if a profile is active, included files are processed with the same profile (using lenient mode — flat files without profile sections are accepted as-is).
  • All include errors are fatal; there is no partial-config fallback.

Security Configuration

The [security] section configures authentication and authorization for the runtime. All sub-sections are optional -- enable only the providers you need.

Placeholders in Camel.toml use ${env:VAR} and ${env:VAR:-default}. The :- separator is native: default is used when VAR is unset. An unset variable without a default aborts config load, naming the field. The legacy {{...}} syntax is rejected with an error pointing at the ${env:} forms. See Environment variable interpolation.

[security.oidc] -- OpenID Connect

Validates JWT tokens against an OIDC issuer.

[security.oidc]
issuer = "https://auth.example.com/realms/my-realm"
jwks_uri = "https://auth.example.com/realms/my-realm/protocol/openid-connect/certs"
audience = ["my-service"]
client_id = "my-service"
client_secret = "${env:OIDC_CLIENT_SECRET}"   # resolved from env
token_endpoint = "https://auth.example.com/realms/my-realm/protocol/openid-connect/token"
introspection_endpoint = "https://auth.example.com/realms/my-realm/protocol/openid-connect/token/introspect"
Field Type Required Description
issuer string yes OIDC issuer URL
jwks_uri string yes JWKS endpoint
audience [string] no Accepted aud claim values
client_id string no OAuth2 client ID
client_secret string no OAuth2 client secret (redacted in debug output)
token_endpoint string no Override token endpoint
introspection_endpoint string no Override introspection endpoint

[security.native] -- Native / Static Authentication

Static credentials for development or trusted-network deployments.

[security.native]
subject = "system"
issuer = "rust-camel-native"
bearer_token = "${env:SYSTEM_BEARER_TOKEN}"
roles = ["admin", "operator"]
scopes = ["read", "write"]

# Optional: multiple named credentials (machine-to-machine style). Each
# entry binds a `subject` to a credential supplied inline (`secret`) or by
# environment-variable reference (`secret_env`).
[[security.native.credentials]]
subject = "svc-orders"
secret_env = "ORDERS_SECRET"
roles = ["service"]
scopes = ["read:orders", "write:orders"]
Field Type Required Description
subject string yes Static subject identity
issuer string no Issuer claim for the native identity
bearer_token string no Static bearer token (redacted in debug output)
api_key string no Static API key (redacted in debug output)
roles [string] no Granted roles
scopes [string] no Granted OAuth scopes
credentials [table] no Named credentials (NativeCredentialEntry); each entry sets subject, exactly one of secret_env / secret, and optional roles / scopes

[security.keycloak] -- Keycloak Integration

Full Keycloak integration with local JWT validation, introspection caching, and optional UMA authorization.

[security.keycloak]
server_url = "https://keycloak.example.com"
realm = "my-realm"
client_id = "my-service"
client_secret = "${env:KEYCLOAK_CLIENT_SECRET}"    # redacted in debug output

[security.keycloak.validation]
method = "local"               # "local" (default) or "introspection"
audience = ["my-service"]
clock_skew_secs = 30

[security.keycloak.jwks]
cache_ttl_secs = 3600          # JWKS cache TTL (default: 3600)
refresh_skew_secs = 60         # Refresh before expiry (default: 60)

[security.keycloak.introspection]
max_entries = 10000            # LRU cache size (default: 10000)
default_ttl_secs = 60          # Positive cache TTL (default: 60)
negative_ttl_secs = 5          # Negative cache TTL (default: 5)

# Optional: Keycloak UMA (User-Managed Access) authorization
[security.keycloak.uma]
provider = "keycloak-uma"

[security.keycloak.uma.cache]
positive_ttl_secs = 30         # default: 30
negative_ttl_secs = 5          # default: 5
max_entries = 10000            # default: 10000
Field Type Required Description
server_url string yes Keycloak base URL
realm string yes Realm name
client_id string yes OAuth2 client ID
client_secret string yes OAuth2 client secret (redacted in debug output)
validation.method string no "local" (default) or "introspection"
validation.audience [string] no Accepted audience values
validation.clock_skew_secs u64 no Allowed clock skew (default: 30)
jwks.cache_ttl_secs u64 no JWKS cache TTL (default: 3600)
jwks.refresh_skew_secs u64 no Pre-expiry refresh window (default: 60)
introspection.max_entries usize no Cache capacity (default: 10000)
introspection.default_ttl_secs u64 no Positive cache TTL (default: 60)
introspection.negative_ttl_secs u64 no Negative cache TTL (default: 5)
uma.provider string no UMA provider name (e.g. "keycloak-uma")
uma.cache.* table no Permission cache tuning (same fields as PermissionCacheConfig)

[security.permissions] -- Permission Providers

Named permission evaluator configurations. Each entry registers a provider with its own cache settings.

[security.permissions.resource-access]
provider = "keycloak-uma"
path = "/resource-check"

[security.permissions.resource-access.cache]
positive_ttl_secs = 30
negative_ttl_secs = 5
max_entries = 10000
Field Type Required Description
provider string yes Provider implementation name
path string no Provider-specific path or endpoint
config map no Extra key-value provider config
cache.positive_ttl_secs u64 no Positive cache TTL (default: 30)
cache.negative_ttl_secs u64 no Negative cache TTL (default: 5)
cache.max_entries usize no LRU cache capacity (default: 10000)

[security.policies.wasm.<name>] -- WASM Security Policies

Named WASM security policies. Each entry registers a WasmSecurityPolicy in the SecurityPolicyRegistry and is referenced from YAML by <name> (see camel-dsl README).

[security.policies.wasm.corp-auth]
path = "plugins/authz.wasm"

[security.policies.wasm.corp-auth.limits]
timeout-secs = 30
max-memory = 52428800

[security.policies.wasm.corp-auth.config]
ldap_url = "ldap://corp"
Field Type Required Description
path string yes Path to the .wasm file (relative to project root or absolute)
limits.timeout-secs u64 no Per-evaluation timeout (default per WasmLimitsConfig)
limits.max-memory u64 no Maximum guest memory in bytes
config map no Key-value pairs passed to the guest's init() function

The runtime sorts config by key before passing it to init() (deterministic ordering across runs). Unknown keys are rejected (deny_unknown_fields).

Profile Selection

Set the active profile via the CAMEL_PROFILE environment variable:

# Use production profile
export CAMEL_PROFILE=production
cargo run

# Use development profile
export CAMEL_PROFILE=development
cargo run

If not set, defaults to the [default] profile only.

Environment Variables

Override any configuration value with environment variables using the CAMEL_ prefix:

# Override log level
export CAMEL_LOG_LEVEL=debug

# Override watch mode
export CAMEL_WATCH=true

# Optional: enable runtime durability/replay by configuring a journal path
export CAMEL_RUNTIME_JOURNAL_PATH=.camel/runtime-events.jsonl

# Override supervision settings
export CAMEL_SUPERVISION_INITIAL_DELAY_MS=2000
export CAMEL_SUPERVISION_MAX_ATTEMPTS=10

# Override routes
export CAMEL_ROUTES='["routes/*.yaml", "routes/extra/*.yaml"]'

Environment variables take precedence over file configuration and are applied after profile merging.

Usage

Basic Usage

use camel_config::CamelConfig;

// Load from Camel.toml in current directory
let config = CamelConfig::from_file("Camel.toml")?;

// Access configuration values
println!("Log level: {}", config.log_level);
println!("Routes: {:?}", config.routes);

With Profile

use camel_config::CamelConfig;

// Load with profile (reads CAMEL_PROFILE env var or uses default)
let config = CamelConfig::from_file_with_profile("Camel.toml")?;

// The profile is automatically merged with [default]

With Environment Variables

use camel_config::CamelConfig;

// Load with full override support (profile + env vars)
let config = CamelConfig::from_file_with_env("Camel.toml")?;

// Environment variables override all other sources

Configure CamelContext with Component Defaults

Use configure_context() to automatically apply component defaults to the CamelContext:

use camel_config::CamelConfig;
use camel_core::CamelContext;
use camel_component_http::HttpComponent;
use camel_component_kafka::KafkaComponent;

// Load configuration
let config = CamelConfig::from_file_with_env("Camel.toml")?;

// Configure context with component defaults from Camel.toml
// Inside an async function
let mut ctx = CamelConfig::configure_context(&config).await?;

// Register component bundles — each bundle reads its own config key from [components.*]
use camel_component_api::ComponentBundle;

if let Some(raw) = config.components.raw.get("http").cloned() {
    camel_component_http::HttpBundle::from_toml(raw)?.register_all(&mut ctx);
}

if let Some(raw) = config.components.raw.get("kafka").cloned() {
    camel_component_kafka::KafkaBundle::from_toml(raw)?.register_all(&mut ctx);
}

Accessing Supervision Configuration

use camel_config::CamelConfig;

let config = CamelConfig::from_file("Camel.toml")?;

if let Some(supervision) = &config.supervision {
    println!("Initial delay: {}ms", supervision.initial_delay_ms);
    println!("Backoff multiplier: {}", supervision.backoff_multiplier);
    println!("Max delay: {}ms", supervision.max_delay_ms);
    println!("Max attempts: {:?}", supervision.max_attempts);
}

Accessing Component Defaults Programmatically

Component config is stored as raw TOML values under [components.*] and owned by each bundle:

use camel_config::CamelConfig;

let config = CamelConfig::from_file("Camel.toml")?;

// Check if HTTP config is present in the raw map
if let Some(raw) = config.components.raw.get("http") {
    println!("HTTP config present: {:?}", raw);
}

// Parse the config via the bundle (same as what camel-cli does)
use camel_component_api::ComponentBundle;
if let Some(raw) = config.components.raw.get("kafka").cloned() {
    let bundle = camel_component_kafka::KafkaBundle::from_toml(raw)?;
    println!("Kafka bundle ready");
}

Installation

Add to your Cargo.toml:

[dependencies]
camel-config = "*"

Related Crates

  • camel-core - Core framework and context
  • camel-dsl - YAML and JSON route definitions and parsing
  • camel-processor - EIP processors and middleware patterns

Documentation

For more details, see the API documentation and the main Rust Camel project.