Skip to main content

api_tools/server/axum/layers/
request_id.rs

1//! Request ID middleware
2
3use axum::http::{HeaderName, Request};
4use std::sync::LazyLock;
5use tower_http::request_id::{MakeRequestId, RequestId};
6use uuid::Uuid;
7
8#[derive(Clone, Copy)]
9pub struct MakeRequestUuid;
10
11/// Request ID header
12pub static REQUEST_ID_HEADER: LazyLock<HeaderName> = LazyLock::new(|| HeaderName::from_static("x-request-id"));
13
14impl MakeRequestId for MakeRequestUuid {
15    fn make_request_id<B>(&mut self, _request: &Request<B>) -> Option<RequestId> {
16        let id = Uuid::new_v4().to_string().parse();
17        match id {
18            Ok(id) => Some(RequestId::new(id)),
19            _ => None,
20        }
21    }
22}
23
24#[cfg(test)]
25mod tests {
26    use super::*;
27    use axum::body::Body;
28
29    #[test]
30    fn test_request_id_header_name() {
31        assert_eq!(REQUEST_ID_HEADER.as_str(), "x-request-id");
32    }
33
34    #[test]
35    fn test_make_request_uuid_returns_valid_uuid() {
36        let mut maker = MakeRequestUuid;
37        let request = Request::builder().body(Body::empty()).unwrap();
38
39        let id = maker.make_request_id(&request).expect("must produce a request id");
40        let value = id.header_value().to_str().expect("ascii");
41
42        assert!(
43            Uuid::parse_str(value).is_ok(),
44            "expected a parseable UUID, got {value:?}"
45        );
46    }
47
48    #[test]
49    fn test_make_request_uuid_is_unique_across_calls() {
50        let mut maker = MakeRequestUuid;
51        let request = Request::builder().body(Body::empty()).unwrap();
52
53        let id_a = maker.make_request_id(&request).unwrap();
54        let id_b = maker.make_request_id(&request).unwrap();
55
56        assert_ne!(
57            id_a.header_value().to_str().unwrap(),
58            id_b.header_value().to_str().unwrap(),
59        );
60    }
61}