Skip to main content

camel_component_api/
component_context.rs

1use std::sync::Arc;
2
3use camel_api::{
4    AsyncHealthCheck, ComponentMetricsLevers, InFlightGauge, MetricsCollector, PlatformService,
5};
6use camel_language_api::Language;
7use tokio_util::sync::CancellationToken;
8
9use crate::Component;
10
11/// Runtime context passed to components during endpoint creation.
12pub trait ComponentContext: Send + Sync {
13    /// Resolve a component by scheme.
14    fn resolve_component(&self, scheme: &str) -> Option<Arc<dyn Component>>;
15
16    /// Resolve a language by name.
17    fn resolve_language(&self, name: &str) -> Option<Arc<dyn Language>>;
18
19    /// Access the active metrics collector.
20    fn metrics(&self) -> Arc<dyn MetricsCollector>;
21
22    /// Context-global gauge of accepted-not-completed exchanges
23    /// (drainclaim). Production contexts return the gauge installed on
24    /// every `ConsumerContext` at consumer start and read by
25    /// `CamelContext::total_in_flight()` for the drain verdict. Default
26    /// `None` keeps test contexts uncounted.
27    fn in_flight_counter(&self) -> Option<Arc<InFlightGauge>> {
28        None
29    }
30
31    /// Snapshot of the `[observability.metrics].components` configuration —
32    /// resolved per component by
33    /// `RuntimeObservability::component_metrics(component)`. The scoped
34    /// identity (not a global bool) lets an override enable one component
35    /// series while the coarse `components` default stays off.
36    ///
37    /// The snapshot gates only the uniform component-operations family;
38    /// error-family emission is never lever-gated. Default: all off
39    /// (opt-in), so unconfigured contexts keep the component series
40    /// suppressed. `CamelContext` overrides this with its
41    /// `MetricsLeversConfig` snapshot.
42    fn component_metric_levers(&self) -> ComponentMetricsLevers {
43        ComponentMetricsLevers::default()
44    }
45
46    /// Access the active health-check registry.
47    ///
48    /// Used by component code paths that need to pin a route Unhealthy
49    /// (category (g) per ADR-0012). Default: NoOp — tests/examples inherit
50    /// the no-op. Concrete runtimes (CamelContext) override to return the
51    /// real registry.
52    fn health(&self) -> Arc<dyn crate::HealthCheckRegistry> {
53        Arc::new(crate::NoOpHealthCheckRegistry)
54    }
55
56    /// Clone of the Runtime-owned shutdown token when this context is
57    /// bound to one. Producer-side and processor-side code (which has no
58    /// `ConsumerContext`) uses it to observe Runtime shutdown.
59    /// `CamelContext` overrides this with its `shutdown_token()`. Default
60    /// `None` keeps unbound contexts (tests, examples) out of the
61    /// shutdown lineage, so callers mint a local root token instead.
62    /// Callers resolve this per call: CamelContext replaces its shutdown
63    /// token on every start, so a token captured once goes stale across
64    /// stop/start.
65    ///
66    /// # Binding-time boundary
67    ///
68    /// Token lineage binds at component REGISTRATION time. Production
69    /// registration sites construct slot-bound contexts
70    /// (`RegistryComponentContext::with_shutdown_slot`, re-written by
71    /// every `CamelContext::start`) and hand them to long-lived components
72    /// (for example `WasmComponent`'s captured `Arc<dyn ComponentContext>`).
73    /// Endpoint-creation-time adapter contexts
74    /// (`ControllerComponentContext`, `MasterDelegateContext`) must NOT
75    /// snapshot tokens; they keep the `None` default so no stale-across
76    /// stop/start lineage can leak, and callers mint a local root instead.
77    fn shutdown_token(&self) -> Option<CancellationToken> {
78        None
79    }
80
81    /// Access the active platform service.
82    fn platform_service(&self) -> Arc<dyn PlatformService>;
83
84    fn register_route_health_check(&self, route_id: &str, check: Arc<dyn AsyncHealthCheck>);
85
86    fn unregister_route_health_check(&self, route_id: &str);
87
88    fn route_id(&self) -> Option<&str> {
89        None
90    }
91
92    fn register_current_route_health_check(&self, check: Arc<dyn AsyncHealthCheck>) {
93        if let Some(id) = self.route_id() {
94            self.register_route_health_check(id, check);
95        }
96    }
97}
98
99/// Default no-op component context for tests/examples.
100pub struct NoOpComponentContext;
101
102impl ComponentContext for NoOpComponentContext {
103    fn resolve_component(&self, _scheme: &str) -> Option<Arc<dyn Component>> {
104        None
105    }
106
107    fn resolve_language(&self, _name: &str) -> Option<Arc<dyn Language>> {
108        None
109    }
110
111    fn metrics(&self) -> Arc<dyn MetricsCollector> {
112        Arc::new(camel_api::NoOpMetrics)
113    }
114
115    fn platform_service(&self) -> Arc<dyn PlatformService> {
116        Arc::new(camel_api::NoopPlatformService::default())
117    }
118
119    fn register_route_health_check(&self, _route_id: &str, _check: Arc<dyn AsyncHealthCheck>) {}
120
121    fn unregister_route_health_check(&self, _route_id: &str) {}
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn component_context_health_default_is_noop() {
130        let ctx = NoOpComponentContext;
131        let h = ctx.health();
132        // Must not panic.
133        h.force_unhealthy_for_route("any", "any", "any");
134    }
135
136    #[test]
137    fn default_component_metric_levers_are_off() {
138        let ctx = NoOpComponentContext;
139        assert!(
140            !ctx.component_metric_levers().enabled_for("http"),
141            "default levers must keep the component series off"
142        );
143    }
144
145    #[test]
146    fn shutdown_token_default_is_none() {
147        let ctx = NoOpComponentContext;
148        assert!(
149            ctx.shutdown_token().is_none(),
150            "unbound contexts must return None so callers mint a local root"
151        );
152    }
153}