camel_template/component.rs
1//! `template:` scheme Component for the external template component
2//! (ADR-0047 Stage 2, Phase 4 / Task 4.3).
3//!
4//! [`TemplateComponent`] is the factory registered under the URI scheme
5//! `template`. It is a thin parse-only adapter: it parses the URI, resolves
6//! the operator-supplied resource limits, and constructs a
7//! [`TemplateEndpoint`] — it performs NO filesystem access. The actual
8//! template acquisition and compilation happen inside the endpoint's
9//! lifecycle `start()` (Task 4.4).
10//!
11//! [`TemplateEndpoint`]: crate::endpoint::TemplateEndpoint
12
13use camel_api::CamelError;
14use camel_component_api::{Component, ComponentContext, Endpoint};
15use camel_language_api::MinijinjaLimitsConfig;
16
17use crate::config::ExternalTemplateLimitsConfig;
18use crate::endpoint::TemplateEndpoint;
19use crate::uri;
20
21/// Factory for `template:` scheme Endpoints (ADR-0047 Stage 2).
22///
23/// Constructible from operator-supplied configs via [`TemplateComponent::new`];
24/// `Default` derives `MinijinjaLimitsConfig::default()` per the spec. Used by
25/// the bundle (Task 4.5) to register the `template` scheme into a
26/// `CamelContext`.
27#[derive(Debug, Default)]
28pub struct TemplateComponent {
29 limits: ExternalTemplateLimitsConfig,
30 render_limits: MinijinjaLimitsConfig,
31}
32
33impl TemplateComponent {
34 /// Build a component from operator-supplied resource-limit configs.
35 pub fn new(limits: ExternalTemplateLimitsConfig, render_limits: MinijinjaLimitsConfig) -> Self {
36 Self {
37 limits,
38 render_limits,
39 }
40 }
41}
42
43impl Component for TemplateComponent {
44 fn scheme(&self) -> &str {
45 "template"
46 }
47
48 /// Parse-only endpoint creation. The URI is validated and the
49 /// operator-supplied limits are resolved (a zero value fails closed at
50 /// this seam), but the filesystem is NOT touched — the heavy lifting
51 /// happens in the endpoint's `lifecycle().start()` (Task 4.4).
52 fn create_endpoint(
53 &self,
54 uri_str: &str,
55 ctx: &dyn ComponentContext,
56 ) -> Result<Box<dyn Endpoint>, CamelError> {
57 let config = uri::parse_template_uri(uri_str, self.limits.clone())?;
58 // Fail closed on a zero operator-supplied limit. `resolve()`
59 // returns `TemplateReloadError`, which has a `From` impl that maps
60 // to `CamelError::TemplateReload(_)` (see `crate::error`).
61 let limits = config.limits.resolve()?;
62 let route_id = ctx.route_id().unwrap_or("template-endpoint").to_string();
63 Ok(Box::new(TemplateEndpoint::new(
64 uri_str.to_string(),
65 config.entry_abs_path,
66 limits,
67 self.render_limits.clone(),
68 route_id,
69 )))
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 /// The component must register under the URI scheme `template`.
78 /// Anything else breaks the bundle (Task 4.5) and every `to:`
79 /// `template:file:///...` route.
80 #[test]
81 fn component_scheme_is_template() {
82 let component = TemplateComponent::default();
83 assert_eq!(component.scheme(), "template");
84 }
85}