Skip to main content

nidus_http/
middleware.rs

1//! Tower middleware helpers.
2
3use std::{borrow::Cow, time::Duration};
4
5use http::{HeaderValue, Method, Request};
6use tower::{limit::RateLimitLayer as TowerRateLimitLayer, timeout::TimeoutLayer};
7use tower_http::compression::CompressionLayer;
8use tower_http::cors::{Any, CorsLayer};
9use tower_http::trace::{HttpMakeClassifier, MakeSpan, TraceLayer};
10use tracing::{Level, Span};
11
12mod api_defaults;
13mod catch_panic;
14mod metrics;
15mod rate_limit;
16mod request_context;
17mod request_id;
18mod request_scope;
19mod security;
20
21pub use crate::context::{
22    ClientKind, IdentityExtractor, RequestContext, RequestIdentity, api_key_identity,
23    client_ip_identity, context_identity, trusted_proxy_client_ip_identity,
24};
25pub use api_defaults::ApiDefaults;
26pub use catch_panic::{CatchPanicLayer, CatchPanicService, catch_panic_layer};
27pub use metrics::{
28    HttpMetricsHook, MetricsLayer, MetricsService, PrometheusMetrics, metrics_layer,
29    route_metrics_layer,
30};
31pub use rate_limit::{
32    InMemoryRateLimitStore, RateLimitConfig, RateLimitDecision, RateLimitError, RateLimitLayer,
33    RateLimitService, RateLimitStore,
34};
35pub use request_context::{RequestContextLayer, RequestContextService, request_context_layer};
36pub use request_id::{
37    RequestIdConfig, RequestIdLayer, RequestIdMode, RequestIdPolicy, RequestIdService,
38    ValidatedRequestIdLayer, ValidatedRequestIdService, validated_request_id_layer,
39};
40pub use request_scope::{RequestScopeLayer, RequestScopeService, request_scope_layer};
41pub use security::{
42    BodyLimitLayer, BodyLimitService, SecurityHeadersLayer, SecurityHeadersService,
43    TimeoutResponseLayer, TimeoutResponseService, body_limit_layer, security_headers_layer,
44    streaming_body_limit_layer, timeout_response_layer, webhook_body_limit_layer,
45};
46
47/// Creates a Tower timeout layer.
48pub fn timeout_layer(timeout: Duration) -> TimeoutLayer {
49    TimeoutLayer::new(timeout)
50}
51
52/// Creates a Tower rate limit layer.
53pub fn rate_limit_layer(num: u64, per: Duration) -> TowerRateLimitLayer {
54    TowerRateLimitLayer::new(num, per)
55}
56
57/// Creates the legacy response-only request-id layer.
58///
59/// This helper mirrors any inbound `x-request-id` to the response or generates
60/// a `nidus-<timestamp>` value when absent. It does not validate IDs or insert
61/// [`RequestContext`]. Use [`validated_request_id_layer`] for production UUID
62/// v4 request IDs and request context population.
63pub fn request_id_layer() -> RequestIdLayer {
64    RequestIdLayer
65}
66
67/// Creates a permissive CORS layer for API development and examples.
68pub fn cors_layer() -> CorsLayer {
69    CorsLayer::new()
70        .allow_origin(Any)
71        .allow_methods([
72            Method::GET,
73            Method::POST,
74            Method::PUT,
75            Method::PATCH,
76            Method::DELETE,
77            Method::OPTIONS,
78        ])
79        .allow_headers(Any)
80}
81
82/// Creates a CORS layer for a single explicit API origin.
83pub fn cors_origin_layer(origin: HeaderValue) -> CorsLayer {
84    CorsLayer::new()
85        .allow_origin(origin)
86        .allow_methods([
87            Method::GET,
88            Method::POST,
89            Method::PUT,
90            Method::PATCH,
91            Method::DELETE,
92            Method::OPTIONS,
93        ])
94        .allow_headers(Any)
95}
96
97/// Creates a gzip response compression layer.
98pub fn compression_layer() -> CompressionLayer {
99    CompressionLayer::new()
100}
101
102/// Creates an HTTP tracing layer for requests and responses.
103pub fn trace_layer() -> TraceLayer<HttpMakeClassifier> {
104    TraceLayer::new_for_http()
105}
106
107/// Creates an HTTP tracing layer that records a stable route label.
108pub fn route_trace_layer(
109    route: impl Into<Cow<'static, str>>,
110) -> TraceLayer<HttpMakeClassifier, RouteMakeSpan> {
111    TraceLayer::new_for_http().make_span_with(RouteMakeSpan::new(route))
112}
113
114/// Span maker that records request method, URI, and a stable route label.
115#[derive(Clone, Debug)]
116pub struct RouteMakeSpan {
117    route: Cow<'static, str>,
118}
119
120impl RouteMakeSpan {
121    /// Creates a route-labelled span maker.
122    pub fn new(route: impl Into<Cow<'static, str>>) -> Self {
123        Self {
124            route: route.into(),
125        }
126    }
127}
128
129impl<B> MakeSpan<B> for RouteMakeSpan {
130    fn make_span(&mut self, request: &Request<B>) -> Span {
131        tracing::span!(
132            Level::DEBUG,
133            "request",
134            method = %request.method(),
135            uri = %request.uri(),
136            route = %self.route,
137        )
138    }
139}