darpan 0.1.0

Linux developer service monitoring utility with auto-detection, real-time health checks, and interactive TUI for databases, APIs, Docker containers, and more
Documentation
use crate::health::HealthCheckerTrait;
use crate::models::{HealthCheckType, HealthResult, Service};
use async_trait::async_trait;
use reqwest::Client;
use std::time::{Duration, Instant};
use tracing::trace;

pub struct HttpHealthChecker {
    client: Client,
}

impl HttpHealthChecker {
    pub fn new(timeout: Duration) -> Self {
        let client = Client::builder()
            .timeout(timeout)
            .build()
            .unwrap();
        
        Self { client }
    }

    async fn check_endpoint(&self, url: &str, expected_status: Option<u16>) -> HealthResult {
        let start = Instant::now();
        
        match self.client.get(url).send().await {
            Ok(response) => {
                let response_time = start.elapsed().as_millis() as u64;
                let status = response.status().as_u16();
                
                if let Some(expected) = expected_status {
                    if status == expected {
                        trace!("HTTP check OK: {} returned {} ({}ms)", url, status, response_time);
                        HealthResult::healthy(response_time)
                    } else {
                        trace!("HTTP check failed: {} returned {} (expected {})", url, status, expected);
                        HealthResult::unhealthy(
                            format!("HTTP status {} (expected {})", status, expected),
                            None,
                        )
                    }
                } else if (200..300).contains(&status) {
                    trace!("HTTP check OK: {} returned {} ({}ms)", url, status, response_time);
                    HealthResult::healthy(response_time)
                } else {
                    trace!("HTTP check warning: {} returned {}", url, status);
                    HealthResult::degraded(
                        response_time,
                        format!("HTTP status {}", status),
                    )
                }
            }
            Err(e) => {
                trace!("HTTP check failed: {} - {}", url, e);
                HealthResult::unhealthy(
                    format!("HTTP request failed: {}", e),
                    Some(format!("Check if service is running and accessible at {}", url)),
                )
            }
        }
    }
}

#[async_trait]
impl HealthCheckerTrait for HttpHealthChecker {
    async fn check(&self, service: &Service) -> HealthResult {
        match &service.health_check.check_type {
            HealthCheckType::Http { path, expected_status } => {
                let url = format!("http://{}:{}{}", service.host, service.port, path);
                self.check_endpoint(&url, *expected_status).await
            }
            _ => {
                // Try default health endpoints
                let default_paths = vec!["/health", "/api/health", "/healthz", "/ping"];
                
                for path in default_paths {
                    let url = format!("http://{}:{}{}", service.host, service.port, path);
                    let result = self.check_endpoint(&url, None).await;
                    
                    if matches!(result.status, crate::models::HealthStatus::Healthy) {
                        return result;
                    }
                }
                
                // If no health endpoint works, just check if port is reachable
                HealthResult::unhealthy(
                    "No health endpoint found".to_string(),
                    Some("Configure a health check endpoint in .darpan.yml".to_string()),
                )
            }
        }
    }

    fn supports(&self, service: &Service) -> bool {
        matches!(service.health_check.check_type, HealthCheckType::Http { .. })
            || matches!(service.service_type, crate::models::ServiceType::HttpServer)
    }
}