soma_http_server/
health.rs1use std::future::Future;
15
16use axum::{routing::get, Router};
17use soma_http_api::probe::{liveness_response, readiness_response};
18
19pub fn liveness_router<S>() -> Router<S>
21where
22 S: Clone + Send + Sync + 'static,
23{
24 Router::new().route("/health", get(|| async { liveness_response() }))
25}
26
27pub fn readiness_router<S, F, Fut, E>(check: F) -> Router<S>
33where
34 S: Clone + Send + Sync + 'static,
35 F: Fn() -> Fut + Clone + Send + Sync + 'static,
36 Fut: Future<Output = Result<(), E>> + Send,
37 E: std::fmt::Display,
38{
39 Router::new().route(
40 "/readyz",
41 get(move || {
42 let check = check.clone();
43 async move { readiness_response(check().await) }
44 }),
45 )
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use axum::{body::to_bytes, body::Body, http::Request, http::StatusCode};
52 use tower::ServiceExt;
53
54 #[tokio::test]
55 async fn liveness_router_reports_ok() {
56 let app: Router<()> = liveness_router();
57 let request = Request::builder()
58 .uri("/health")
59 .body(Body::empty())
60 .unwrap();
61 let response = app.oneshot(request).await.unwrap();
62 assert_eq!(response.status(), StatusCode::OK);
63 let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
64 let value: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
65 assert_eq!(value["status"], "ok");
66 }
67
68 #[tokio::test]
69 async fn readiness_router_reports_ready_when_check_succeeds() {
70 let app: Router<()> = readiness_router(|| async { Ok::<(), &'static str>(()) });
71 let request = Request::builder()
72 .uri("/readyz")
73 .body(Body::empty())
74 .unwrap();
75 let response = app.oneshot(request).await.unwrap();
76 assert_eq!(response.status(), StatusCode::OK);
77 }
78
79 #[tokio::test]
80 async fn readiness_router_reports_unavailable_when_check_fails() {
81 let app: Router<()> =
82 readiness_router(|| async { Err::<(), &'static str>("upstream down") });
83 let request = Request::builder()
84 .uri("/readyz")
85 .body(Body::empty())
86 .unwrap();
87 let response = app.oneshot(request).await.unwrap();
88 assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
89 let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
90 let value: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
91 assert_eq!(value["reason"], "upstream down");
92 }
93}