camel-template
External template component for rust-camel (MiniJinja, file-based, ADR-0047 Stage 2)
Overview
The Template component renders MiniJinja templates loaded from the filesystem against the body and headers of each inbound exchange. It is producer-only — you place it on the to: side of a route to transform the exchange body into rendered output.
Templates are compiled once at route startup (fail-closed) and cached for zero-filesystem-I/O hot-path rendering. The component supports hot-reload: a control-plane ReloadTemplates command re-acquires the dependency closure, recompiles, and atomically swaps the compiled set without disturbing in-flight renders.
Features
- File-based: Templates live in standard
.html.tmplfiles on disk - Fail-closed: Compilation errors prevent the route from starting; render errors preserve the original exchange body unchanged
- Zero-override: The template source and root directory are operator-configured at startup — no exchange header or property can override them
- Bounded acquisition: Configurable limits on total source bytes, include count/depth, single-template size, and reload wall-clock timeout
- Bounded render: Per-render limits on context size, output size, fuel, recursion depth, and execution timeout (inherited from the MiniJinja language engine)
- Atomic hot-reload: Dependency-closure re-acquisition, compilation, and swap — prior set retained on failure
- openat-based confinement: All file reads go through
openat-relative handles;.., symlinks, absolute paths, and cycles are rejected
Installation
Add to your Cargo.toml:
[]
= "*"
URI Format
template:file:///<absolute-path-to-template>
The URI has two parts:
| Part | Description |
|---|---|
template |
Outer scheme identifying this component |
file:///<abs-path> |
Inner scheme with an absolute filesystem path to the entry template |
Bare paths (template:/srv/t/page.html) and non-file: inner schemes are rejected at endpoint construction. The path must be absolute and free of .. segments.
Usage
Basic Template Render
use RouteBuilder;
use CamelContext;
use LogLevel;
use TemplateComponent;
use ExternalTemplateLimitsConfig;
use MinijinjaLimitsConfig;
use Value;
async
Template file (/srv/templates/page.html.tmpl):
{% autoescape "html" -%}
{{ title }}
{{ title }}
Hello, {{ body }}!
{%- endautoescape %}
The exchange body becomes the {{ body }} variable in the template context. All exchange headers are also available as top-level variables.
Template with Exchange Context
let route = from
.route_id
.set_body
.set_header
.set_header
.to
.to
.build?;
Template (email.html.tmpl):
{% autoescape "html" -%}
Dear {{ customer_name }},
{{ body }}
Total: {{ total }}
{%- endautoescape %}
Configuration via Camel.toml
[]
= 33554432
= 128
= 2097152
= 10000
[]
= 65536
= 1048576
= 100000
Error Behavior
| Scenario | Error | Behavior |
|---|---|---|
| Missing template file on startup | CamelError::TemplateReload |
Route fails to start (fail-closed) |
| Template compilation error | CamelError::TemplateReload |
Route fails to start; error details logged |
Body::Stream at render time |
CamelError::ProcessorError |
Body unchanged, error propagated |
| Strict-undefined variable access | CamelError::ProcessorError |
Body unchanged, error propagated |
| Output size exceeds limit | CamelError::ProcessorError |
Render halted, body unchanged |
| Execution timeout | CamelError::ProcessorError |
Render cancelled, body unchanged |
| Bounded-acquisition exceeded | CamelError::TemplateReload |
Route fails to start (fail-closed) |
On every render failure, the exchange body is not mutated — the inbound body reaches the error handler byte-identical to what was submitted.
Empty body caveat
An empty inbound body (Body::Empty) is exposed to the template as null. Under minijinja's default stringification, {{ body }} renders the literal word none — not an empty string, and not an error (strict-undefined does not trip because the body key is present). If a route may receive empty bodies, guard the reference explicitly:
{% if body %}{{ body }}{% endif %}
{# or #}
{{ body | default("") }}
See bd rc-wnqj for a planned shared-engine fix.
Documentation
License
Apache-2.0
Contributing
Contributions are welcome! Please see the main repository for details.