github-mcp 0.1.0

GitHub v3 REST API MCP server, generated by mcpify.
Documentation
// GitHub v3 REST API MCP server — generated by mcpify. Do not hand-edit.

use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;

use tokio::sync::Mutex;

use super::component_registry::{ComponentRegistry, ComponentStatus};

pub type HealthCheckFuture = Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>>;
pub type HealthCheckFn = Box<dyn Fn() -> HealthCheckFuture + Send + Sync>;

const DEFAULT_INTERVAL: Duration = Duration::from_secs(30);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);

struct RegisteredCheck {
    name: String,
    check: HealthCheckFn,
}

/// Runs registered health checks periodically, updating a shared
/// `ComponentRegistry` so degraded-mode startup and a health-check endpoint
/// (plus the `test-connection` CLI command) can report accurately —
/// REQ-2.3.4.
pub struct HealthCheckManager {
    registry: Arc<Mutex<ComponentRegistry>>,
    checks: Vec<RegisteredCheck>,
    interval: Duration,
    timeout: Duration,
}

impl HealthCheckManager {
    pub fn new(registry: Arc<Mutex<ComponentRegistry>>) -> Self {
        Self {
            registry,
            checks: Vec::new(),
            interval: DEFAULT_INTERVAL,
            timeout: DEFAULT_TIMEOUT,
        }
    }

    pub async fn register(&mut self, name: &str, critical: bool, check: HealthCheckFn) {
        self.registry.lock().await.register(name, critical);
        self.checks.push(RegisteredCheck {
            name: name.to_string(),
            check,
        });
    }

    pub async fn run_once(&self) {
        for RegisteredCheck { name, check } in &self.checks {
            let outcome = tokio::time::timeout(self.timeout, check()).await;
            let mut registry = self.registry.lock().await;
            match outcome {
                Ok(Ok(())) => registry.report(name, ComponentStatus::Healthy, None),
                Ok(Err(err)) => {
                    tracing::warn!(component = name, error = %err, "health check failed");
                    registry.report(name, ComponentStatus::Unhealthy, Some(err.to_string()));
                }
                Err(_) => {
                    tracing::warn!(component = name, "health check timed out");
                    registry.report(
                        name,
                        ComponentStatus::Unhealthy,
                        Some("timed out".to_string()),
                    );
                }
            }
        }
    }

    /// Spawns the periodic check loop; the returned handle can be aborted
    /// during shutdown.
    pub fn start(self: Arc<Self>) -> tokio::task::JoinHandle<()> {
        tokio::spawn(async move {
            let mut ticker = tokio::time::interval(self.interval);
            loop {
                ticker.tick().await;
                self.run_once().await;
            }
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn a_passing_check_reports_healthy() {
        let registry = Arc::new(Mutex::new(ComponentRegistry::new()));
        let mut manager = HealthCheckManager::new(registry.clone());
        manager
            .register("db", true, Box::new(|| Box::pin(async { Ok(()) })))
            .await;

        manager.run_once().await;

        assert_eq!(
            registry.lock().await.overall_status(),
            ComponentStatus::Healthy
        );
    }

    #[tokio::test]
    async fn a_failing_check_reports_unhealthy() {
        let registry = Arc::new(Mutex::new(ComponentRegistry::new()));
        let mut manager = HealthCheckManager::new(registry.clone());
        manager
            .register(
                "db",
                true,
                Box::new(|| Box::pin(async { Err(anyhow::anyhow!("connection refused")) })),
            )
            .await;

        manager.run_once().await;

        assert_eq!(
            registry.lock().await.overall_status(),
            ComponentStatus::Unhealthy
        );
    }
}