actionqueue_daemon/http/health.rs
1//! Health check route module.
2//!
3//! This module provides the liveness endpoint (`GET /healthz`) for the daemon.
4//! The health endpoint is side-effect free and provides a deterministic response
5//! indicating daemon liveness only. It does not imply readiness or storage availability.
6//!
7//! # Invariant boundaries
8//!
9//! The health handler performs no IO, reads no storage, and mutates no runtime state.
10//! It is purely a static response intended for watchdogs and orchestration systems
11//! that require reliable liveness signals.
12
13use axum::extract::State;
14use axum::response::IntoResponse;
15use axum::Json;
16use serde::Serialize;
17
18/// Health check response payload.
19///
20/// This struct represents the stable schema for the health endpoint response.
21/// Fields should not be modified without careful consideration of external
22/// dependencies that may rely on this contract.
23#[derive(Debug, Clone, Serialize)]
24pub struct HealthResponse {
25 /// Daemon liveness status.
26 ///
27 /// This field indicates that the daemon process is running and responding
28 /// to HTTP requests. It does not indicate that all services are ready
29 /// or that storage is available.
30 pub status: &'static str,
31}
32
33impl HealthResponse {
34 /// Creates a new health response indicating liveness.
35 pub const fn ok() -> Self {
36 Self { status: "ok" }
37 }
38}
39
40/// Health check handler.
41///
42/// This handler responds to `GET /healthz` requests with a deterministic,
43/// side-effect-free payload indicating daemon liveness.
44///
45/// # Invariant boundaries
46///
47/// This handler performs no IO, reads no storage, and mutates no runtime state.
48#[tracing::instrument(skip_all)]
49pub async fn handle(_state: State<super::RouterState>) -> impl IntoResponse {
50 Json(HealthResponse::ok())
51}
52
53/// Registers the health route in the router builder.
54///
55/// This function adds the `/healthz` endpoint to the router configuration.
56/// The route is always available when HTTP is enabled and does not depend
57/// on any feature flags.
58///
59/// # Arguments
60///
61/// * `router` - The axum router to register routes with
62pub fn register_routes(
63 router: axum::Router<super::RouterState>,
64) -> axum::Router<super::RouterState> {
65 router.route("/healthz", axum::routing::get(handle))
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn test_health_response_ok() {
74 let response = HealthResponse::ok();
75 assert_eq!(response.status, "ok");
76 }
77
78 #[test]
79 fn test_health_response_serialization() {
80 let response = HealthResponse::ok();
81 let json = serde_json::to_string(&response).expect("serialization should succeed");
82 assert_eq!(json, r#"{"status":"ok"}"#);
83 }
84}