kegani 0.1.1

A developer-friendly, ergonomic, production-ready Rust web framework
Documentation
//! Timeout middleware
//!
//! Provides request timeout handling.

use actix_web::{
    dev::{Service, ServiceRequest, ServiceResponse, Transform},
    Error,
};
use std::future::{ready, Ready};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::time::timeout;

/// Timeout configuration
#[derive(Debug, Clone)]
pub struct TimeoutConfig {
    pub duration: Duration,
}

impl TimeoutConfig {
    /// Create a new timeout config with the given duration
    pub fn new(duration: Duration) -> Self {
        Self { duration }
    }
}

/// Timeout middleware
pub struct Timeout {
    config: TimeoutConfig,
}

impl Timeout {
    /// Create a new Timeout middleware
    pub fn new(duration: Duration) -> Self {
        Self {
            config: TimeoutConfig { duration },
        }
    }
}

impl<S, B> Transform<S, ServiceRequest> for Timeout
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Transform = TimeoutMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(TimeoutMiddleware {
            service,
            duration: self.config.duration,
        }))
    }
}

pub struct TimeoutMiddleware<S> {
    service: S,
    duration: Duration,
}

impl<S, B> Service<ServiceRequest> for TimeoutMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>>>>;

    fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let duration = self.duration;
        let path = req.path().to_string();
        let fut = self.service.call(req);

        Box::pin(async move {
            match timeout(duration, fut).await {
                Ok(result) => result,
                Err(_) => {
                    tracing::warn!(path = %path, "Request timeout");
                    Err(actix_web::error::ErrorRequestTimeout(
                        "Request timeout. Please try again."
                    ))
                }
            }
        })
    }
}