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}