Skip to main content

camel_component_api/
component_context.rs

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