app-frame 0.3.1

Compile-time dependency-injected application framework with a service orchestrator.
Documentation
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, Server, StatusCode};
use std::convert::Infallible;
use std::net::SocketAddr;
use std::sync::Arc;

use super::service_manager::ServiceManager;

pub async fn run(manager: Arc<ServiceManager>, config: HealthEndpointConfig) {
    let addr = SocketAddr::from(([0, 0, 0, 0], config.port));
    let make_svc = make_service_fn(move |_conn| {
        let manager = manager.clone();
        let config = config.clone();
        async move {
            Ok::<_, Infallible>(service_fn(move |x| {
                health(manager.clone(), config.clone(), x)
            }))
        }
    });
    if let Err(e) = Server::bind(&addr).serve(make_svc).await {
        tracing::error!("health endpoint exited with error: {}", e);
    }
}

#[derive(Clone)]
pub struct HealthEndpointConfig {
    pub port: u16,
    pub success_status: StatusCode,
    pub fail_status: StatusCode,
}

impl Default for HealthEndpointConfig {
    fn default() -> Self {
        Self {
            port: 3417,
            success_status: StatusCode::OK,
            fail_status: StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

async fn health(
    manager: Arc<ServiceManager>,
    config: HealthEndpointConfig,
    req: Request<Body>,
) -> Result<Response<Body>, Infallible> {
    tracing::debug!("Received http request: {req:?}");
    let mut response = Response::new(Body::empty());
    match (req.method(), req.uri().path()) {
        (&Method::GET, "/health") => {
            let status = manager.check();
            if status.dead.is_empty() {
                tracing::debug!("Responding 200 to health check.");
                *response.status_mut() = config.success_status;
                *response.body_mut() = format!("{} live services", status.alive.len()).into();
            } else {
                tracing::error!("Responding 500 to health check.");
                *response.status_mut() = config.fail_status;
                *response.body_mut() = format!(
                    "{} live services. dead services: {:#?}",
                    status.alive.len(),
                    status.dead
                )
                .into();
            }
        }
        _ => {
            tracing::info!("Responding 404 to unexpected request: {req:?}");
            *response.status_mut() = StatusCode::NOT_FOUND;
        }
    };
    Ok(response)
}