Skip to main content

axum_api_kit/
health.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4    Json,
5};
6use serde::Serialize;
7
8/// A minimal health-check response body.
9///
10/// Serializes as `{ "status": "ok" }` and responds with `200 OK`.
11///
12/// Use [`HealthResponse::degraded`] or [`HealthResponse::unhealthy`] for non-nominal states.
13///
14/// # Example
15///
16/// ```rust
17/// use axum::response::IntoResponse;
18/// use axum_api_kit::HealthResponse;
19///
20/// async fn health() -> impl IntoResponse {
21///     HealthResponse::ok()
22/// }
23/// ```
24#[derive(Debug, Clone, Serialize)]
25pub struct HealthResponse {
26    pub status: &'static str,
27    #[serde(skip)]
28    status_code: StatusCode,
29}
30
31impl HealthResponse {
32    /// Returns a `HealthResponse` with `status = "ok"` and HTTP `200 OK`.
33    pub const fn ok() -> Self {
34        Self {
35            status: "ok",
36            status_code: StatusCode::OK,
37        }
38    }
39
40    /// Returns a `HealthResponse` with `status = "degraded"` and HTTP `200 OK`.
41    ///
42    /// Use when the service is reachable but operating in a reduced capacity
43    /// (e.g., a non-critical dependency is down).
44    pub const fn degraded() -> Self {
45        Self {
46            status: "degraded",
47            status_code: StatusCode::OK,
48        }
49    }
50
51    /// Returns a `HealthResponse` with `status = "unhealthy"` and HTTP `503 Service Unavailable`.
52    ///
53    /// Use when the service cannot fulfill requests normally
54    /// (e.g., the primary database is unreachable).
55    pub const fn unhealthy() -> Self {
56        Self {
57            status: "unhealthy",
58            status_code: StatusCode::SERVICE_UNAVAILABLE,
59        }
60    }
61}
62
63impl IntoResponse for HealthResponse {
64    fn into_response(self) -> Response {
65        (self.status_code, Json(self)).into_response()
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn ok_has_correct_status() {
75        let r = HealthResponse::ok();
76        assert_eq!(r.status, "ok");
77        assert_eq!(r.status_code, StatusCode::OK);
78    }
79
80    #[test]
81    fn degraded_has_correct_status() {
82        let r = HealthResponse::degraded();
83        assert_eq!(r.status, "degraded");
84        assert_eq!(r.status_code, StatusCode::OK);
85    }
86
87    #[test]
88    fn unhealthy_has_correct_status() {
89        let r = HealthResponse::unhealthy();
90        assert_eq!(r.status, "unhealthy");
91        assert_eq!(r.status_code, StatusCode::SERVICE_UNAVAILABLE);
92    }
93
94    #[test]
95    fn serializes_status_field_only() {
96        let r = HealthResponse::ok();
97        let v = serde_json::to_value(&r).unwrap();
98        assert_eq!(v["status"], "ok");
99        assert!(v.get("status_code").is_none());
100    }
101}