use http_extensions::HttpError;
use seatbelt::timeout::{Timeout, TimeoutLayer};
use seatbelt::typestates::Set;
use crate::{HttpRequest, HttpResponse};
pub type HttpTimeoutLayer<S1 = Set, S2 = Set> = TimeoutLayer<HttpRequest, http_extensions::Result<HttpResponse>, S1, S2>;
pub type HttpTimeout<S> = Timeout<HttpRequest, http_extensions::Result<HttpResponse>, S>;
pub trait HttpTimeoutLayerExt<S1, S2>: sealed::Sealed {
fn http_timeout_error(self) -> HttpTimeoutLayer<S1, Set>;
}
impl<S1, S2> HttpTimeoutLayerExt<S1, S2> for HttpTimeoutLayer<S1, S2> {
fn http_timeout_error(self) -> HttpTimeoutLayer<S1, Set> {
self.timeout_error(|args| HttpError::timeout(args.timeout()))
}
}
pub(crate) mod sealed {
use super::*;
#[expect(unnameable_types, reason = "intentional, sealed trait pattern")]
pub trait Sealed {}
impl<S1, S2> Sealed for HttpTimeoutLayer<S1, S2> {}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use std::time::Duration;
use futures::executor::block_on;
use http::StatusCode;
use http_extensions::{FakeHandler, HttpRequestBuilder};
use layered::{Service, Stack};
use seatbelt::{Recovery, RecoveryKind};
use tick::ClockControl;
use super::*;
use crate::HttpResilienceContext;
#[test]
fn http_timeout_error_closure_returns_timeout_kind() {
let timeout = Duration::from_secs(30);
let error = HttpError::timeout(timeout);
assert_eq!(error.recovery().kind(), RecoveryKind::Retry);
}
#[test]
fn timeout_fires_on_slow_handler() {
let handler = FakeHandler::never_completes();
let clock = ClockControl::default().auto_advance_timers(true).to_clock();
let context = HttpResilienceContext::new(&clock);
let service = (
HttpTimeout::layer("test", &context)
.http_timeout_error()
.timeout(Duration::from_secs(5)),
handler,
)
.into_service();
let request = HttpRequestBuilder::new_fake().uri("https://example.com").build().unwrap();
let error = block_on(service.execute(request)).unwrap_err();
assert_eq!(error.recovery().kind(), RecoveryKind::Retry);
}
#[test]
fn fast_handler_succeeds_within_timeout() {
let handler = FakeHandler::from(StatusCode::OK);
let clock = ClockControl::default().auto_advance_timers(true).to_clock();
let context = HttpResilienceContext::new(&clock);
let service = (
HttpTimeout::layer("test", &context)
.http_timeout_error()
.timeout(Duration::from_secs(30)),
handler,
)
.into_service();
let request = HttpRequestBuilder::new_fake().uri("https://example.com").build().unwrap();
let response = block_on(service.execute(request)).unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
}