maps/backend/src/routes/health.rs
2026-03-30 09:22:16 +02:00

111 lines
3.5 KiB
Rust

use actix_web::{web, HttpResponse};
use sqlx::PgPool;
use std::collections::HashMap;
use crate::errors::AppError;
use crate::models::health::{HealthResponse, ServiceHealth};
use crate::services::cache::CacheService;
use crate::services::martin::MartinService;
use crate::services::osrm::OsrmService;
use crate::services::photon::PhotonService;
/// GET /api/health
///
/// Checks connectivity to all upstream services and returns aggregated status.
/// Always returns HTTP 200; the client should inspect the `status` field.
pub async fn health_check(
martin: web::Data<MartinService>,
photon: web::Data<PhotonService>,
osrm: web::Data<OsrmService>,
pool: web::Data<PgPool>,
cache: web::Data<CacheService>,
start_time: web::Data<std::time::Instant>,
) -> Result<HttpResponse, AppError> {
let mut services = HashMap::new();
// Check Martin
let martin_health = check_service("martin", martin.health_check()).await;
services.insert("martin".to_string(), martin_health);
// Check Photon
let photon_health = check_service("photon", photon.health_check()).await;
services.insert("photon".to_string(), photon_health);
// Check OSRM instances
let osrm_driving_health = check_service("osrm_driving", osrm.health_check("driving")).await;
services.insert("osrm_driving".to_string(), osrm_driving_health);
let osrm_walking_health = check_service("osrm_walking", osrm.health_check("walking")).await;
services.insert("osrm_walking".to_string(), osrm_walking_health);
let osrm_cycling_health = check_service("osrm_cycling", osrm.health_check("cycling")).await;
services.insert("osrm_cycling".to_string(), osrm_cycling_health);
// Check PostgreSQL
let postgres_health = check_postgres(&pool).await;
services.insert("postgres".to_string(), postgres_health);
// Check Redis
let redis_health = check_service("redis", cache.ping()).await;
services.insert("redis".to_string(), redis_health);
let status = HealthResponse::compute_status(&services);
let uptime = start_time.elapsed().as_secs();
let response = HealthResponse {
status,
version: "1.0.0".to_string(),
uptime_seconds: uptime,
services,
};
Ok(HttpResponse::Ok()
.content_type("application/json; charset=utf-8")
.insert_header(("Cache-Control", "no-store"))
.json(response))
}
/// Check a service that returns Result<latency_ms, ()>.
async fn check_service(
_name: &str,
future: impl std::future::Future<Output = Result<u64, ()>>,
) -> ServiceHealth {
match future.await {
Ok(latency_ms) => ServiceHealth {
status: if latency_ms > 2000 {
"degraded".to_string()
} else {
"ok".to_string()
},
latency_ms,
},
Err(()) => ServiceHealth {
status: "down".to_string(),
latency_ms: 0,
},
}
}
/// Check PostgreSQL connectivity.
async fn check_postgres(pool: &PgPool) -> ServiceHealth {
let start = std::time::Instant::now();
let result = sqlx::query_scalar::<_, i32>("SELECT 1")
.fetch_one(pool)
.await;
let latency_ms = start.elapsed().as_millis() as u64;
match result {
Ok(_) => ServiceHealth {
status: if latency_ms > 2000 {
"degraded".to_string()
} else {
"ok".to_string()
},
latency_ms,
},
Err(_) => ServiceHealth {
status: "down".to_string(),
latency_ms: 0,
},
}
}