use axum::{extract::State, http::StatusCode, Json};
use serde::{Deserialize, Serialize};
use crate::db::pool::health_check as db_health_check;
use crate::state::AppState;
#[derive(Debug, Serialize, Deserialize)]
pub struct HealthCheckResponse {
pub status: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ApiHealthResponse {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub database: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nats: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uptime_seconds: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PoolStatusResponse {
pub pool_min: u32,
pub pool_max: u32,
pub pool_size: u32,
pub pool_available: u32,
pub requests_waiting: u32,
pub utilization: f64,
pub slots_available: u32,
pub status: String,
}
pub async fn health_check() -> Json<HealthCheckResponse> {
Json(HealthCheckResponse {
status: "ok".to_string(),
})
}
pub async fn api_health(State(state): State<AppState>) -> (StatusCode, Json<ApiHealthResponse>) {
let db_healthy = db_health_check(&state.db).await;
let nats_status = if state.has_nats() {
Some("connected".to_string())
} else {
Some("not_configured".to_string())
};
let overall_status = if db_healthy {
"ok".to_string()
} else {
"unhealthy".to_string()
};
let status_code = if db_healthy {
StatusCode::OK
} else {
StatusCode::SERVICE_UNAVAILABLE
};
let response = ApiHealthResponse {
status: overall_status,
database: Some(if db_healthy {
"connected".to_string()
} else {
"disconnected".to_string()
}),
nats: nats_status,
uptime_seconds: Some(state.uptime_seconds()),
version: Some(env!("CARGO_PKG_VERSION").to_string()),
};
(status_code, Json(response))
}
pub async fn pool_status(State(state): State<AppState>) -> Json<PoolStatusResponse> {
let pool_size = u32::try_from(state.db.size()).unwrap_or(u32::MAX);
let pool_available = u32::try_from(state.db.num_idle()).unwrap_or(u32::MAX);
let pool_max = pool_size.max(pool_available);
let pool_min = 0;
let active = pool_size.saturating_sub(pool_available);
let utilization = if pool_max > 0 {
(active as f64 / pool_max as f64).clamp(0.0, 1.0)
} else {
0.0
};
Json(PoolStatusResponse {
pool_min,
pool_max,
pool_size,
pool_available,
requests_waiting: 0,
utilization,
slots_available: pool_available,
status: "ok".to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_health_check() {
let response = health_check().await;
assert_eq!(response.status, "ok");
}
}