camel_component_api/health_registry.rs
1//! Narrow `HealthCheckRegistry` trait exposed to components via
2//! `RuntimeObservability::health()`.
3//!
4//! This is intentionally NOT the full registry surface — components only need
5//! `force_unhealthy_for_route` (category (g) per ADR-0012). Register/unregister
6//! stays on `ComponentContext` (camel-component-api) and the concrete
7//! `HealthCheckRegistry` struct (camel-core) for runtime-internal callers.
8
9use std::sync::Arc;
10
11/// Narrow view of the health registry exposed to components via
12/// [`crate::RuntimeObservability::health`].
13///
14/// Category (g) endpoint/producer creation failures call
15/// `force_unhealthy_for_route(route_id, "endpoint-creation", reason)` to pin
16/// the pod NotReady (HTTP 503). Supervision restart (ADR-0007) clears the pin
17/// via `register_for_route` once the endpoint is recreated.
18///
19/// There is no `force_degraded_for_route` method and we explicitly do NOT add
20/// one — a half-functional route is worse than a removed-from-rotation one.
21pub trait HealthCheckRegistry: Send + Sync {
22 /// Pin the given route to Unhealthy. Replaces any existing checks for
23 /// the route with a single ForcedUnhealthy entry.
24 ///
25 /// `name`: a stable identifier (e.g. "endpoint-creation", "pool-init").
26 /// `reason`: human-readable detail that will appear in the health report.
27 fn force_unhealthy_for_route(&self, route_id: &str, name: &str, reason: &str);
28}
29
30/// No-op implementation for tests and examples. All methods are silent no-ops.
31#[derive(Debug, Default, Clone)]
32pub struct NoOpHealthCheckRegistry;
33
34impl HealthCheckRegistry for NoOpHealthCheckRegistry {
35 fn force_unhealthy_for_route(&self, _route_id: &str, _name: &str, _reason: &str) {
36 // no-op — test/example only
37 }
38}
39
40/// Convenience constructor.
41pub fn noop_health_check_registry() -> Arc<dyn HealthCheckRegistry> {
42 Arc::new(NoOpHealthCheckRegistry)
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn noop_force_unhealthy_does_not_panic() {
51 let reg = NoOpHealthCheckRegistry;
52 reg.force_unhealthy_for_route("any-route", "any-name", "any reason");
53 // Test passes if no panic.
54 }
55
56 #[test]
57 fn noop_is_send_sync() {
58 fn assert_send_sync<T: Send + Sync>() {}
59 assert_send_sync::<NoOpHealthCheckRegistry>();
60 }
61
62 #[test]
63 fn noop_health_check_registry_returns_arc() {
64 let reg: Arc<dyn HealthCheckRegistry> = noop_health_check_registry();
65 // Construct and confirm Arc coercion works (would fail to compile if signature wrong).
66 // Then exercise the no-op:
67 reg.force_unhealthy_for_route("route-1", "init", "test reason");
68 // No panic = pass.
69 }
70}