Skip to main content

nidus_http/middleware/
request_context.rs

1use std::task::{Context, Poll};
2
3use axum::extract::Request;
4use tower::{Layer, Service};
5
6use crate::context::{RequestContext, header_to_string};
7
8/// Creates a Tower layer that enriches [`RequestContext`] request extensions.
9///
10/// Use this with [`crate::middleware::validated_request_id_layer`] so handlers
11/// can extract [`RequestContext`]. The request ID layer chooses and stores the
12/// final ID; this layer rebuilds the context from request parts so correlation,
13/// trace, route, and client-kind fields reflect the current request boundary.
14/// [`crate::middleware::ApiDefaults::production`] installs both layers.
15///
16/// If no prior context or `x-request-id` header exists, the context uses
17/// `"unknown"` as the request ID. Prefer validated request IDs for production
18/// APIs.
19pub fn request_context_layer() -> RequestContextLayer {
20    RequestContextLayer
21}
22
23/// Tower layer that inserts request/correlation context into request extensions.
24///
25/// The inserted context reads:
26/// - `x-request-id` from the existing [`RequestContext`] or request header
27/// - `x-correlation-id`, falling back to the request ID
28/// - validated `traceparent` trace and parent span IDs
29/// - `x-api-key` / `Authorization` for client classification
30/// - Axum [`axum::extract::MatchedPath`] when available at this layer
31#[derive(Clone, Copy, Debug, Default)]
32pub struct RequestContextLayer;
33
34impl<S> Layer<S> for RequestContextLayer {
35    type Service = RequestContextService<S>;
36
37    fn layer(&self, inner: S) -> Self::Service {
38        RequestContextService { inner }
39    }
40}
41
42/// Service produced by [`RequestContextLayer`].
43#[derive(Clone, Debug)]
44pub struct RequestContextService<S> {
45    inner: S,
46}
47
48impl<S> Service<Request> for RequestContextService<S>
49where
50    S: Service<Request> + Send + 'static,
51    S::Future: Send + 'static,
52    S::Error: Send + 'static,
53{
54    type Response = S::Response;
55    type Error = S::Error;
56    // All context work happens before the inner call, so the inner future can
57    // be returned directly without a per-request box.
58    type Future = S::Future;
59
60    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
61        self.inner.poll_ready(cx)
62    }
63
64    fn call(&mut self, request: Request) -> Self::Future {
65        let (mut parts, body) = request.into_parts();
66        let request_id = parts
67            .extensions
68            .remove::<RequestContext>()
69            .map(RequestContext::into_request_id)
70            .or_else(|| header_to_string(&parts.headers, "x-request-id"))
71            .unwrap_or_else(|| "unknown".to_owned());
72        let context = RequestContext::from_parts(&parts, request_id);
73        parts.extensions.insert(context);
74        self.inner.call(Request::from_parts(parts, body))
75    }
76}