Skip to main content

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}