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    fn shutdown_token(&self) -> Option<CancellationToken> {
59        None
60    }
61
62    /// Access the active platform service.
63    fn platform_service(&self) -> Arc<dyn PlatformService>;
64
65    fn register_route_health_check(&self, route_id: &str, check: Arc<dyn AsyncHealthCheck>);
66
67    fn unregister_route_health_check(&self, route_id: &str);
68
69    fn route_id(&self) -> Option<&str> {
70        None
71    }
72
73    fn register_current_route_health_check(&self, check: Arc<dyn AsyncHealthCheck>) {
74        if let Some(id) = self.route_id() {
75            self.register_route_health_check(id, check);
76        }
77    }
78}
79
80/// Default no-op component context for tests/examples.
81pub struct NoOpComponentContext;
82
83impl ComponentContext for NoOpComponentContext {
84    fn resolve_component(&self, _scheme: &str) -> Option<Arc<dyn Component>> {
85        None
86    }
87
88    fn resolve_language(&self, _name: &str) -> Option<Arc<dyn Language>> {
89        None
90    }
91
92    fn metrics(&self) -> Arc<dyn MetricsCollector> {
93        Arc::new(camel_api::NoOpMetrics)
94    }
95
96    fn platform_service(&self) -> Arc<dyn PlatformService> {
97        Arc::new(camel_api::NoopPlatformService::default())
98    }
99
100    fn register_route_health_check(&self, _route_id: &str, _check: Arc<dyn AsyncHealthCheck>) {}
101
102    fn unregister_route_health_check(&self, _route_id: &str) {}
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn component_context_health_default_is_noop() {
111        let ctx = NoOpComponentContext;
112        let h = ctx.health();
113        // Must not panic.
114        h.force_unhealthy_for_route("any", "any", "any");
115    }
116
117    #[test]
118    fn shutdown_token_default_is_none() {
119        let ctx = NoOpComponentContext;
120        assert!(
121            ctx.shutdown_token().is_none(),
122            "unbound contexts must return None so callers mint a local root"
123        );
124    }
125}