Skip to main content

dig_rpc/middleware/
request_id.rs

1//! Attach a UUID v7 (time-ordered) to every request.
2//!
3//! The request id is propagated through tracing spans and the audit log
4//! so a single request can be traced end-to-end. It is also returned as
5//! the `x-request-id` response header for client-side correlation.
6
7use uuid::Uuid;
8
9/// An opaque per-request identifier.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct RequestId(pub Uuid);
12
13impl RequestId {
14    /// Fresh UUID v7 (time-ordered).
15    pub fn new() -> Self {
16        Self(Uuid::now_v7())
17    }
18
19    /// Hex string representation.
20    pub fn to_string_hex(&self) -> String {
21        self.0.simple().to_string()
22    }
23}
24
25impl Default for RequestId {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31impl std::fmt::Display for RequestId {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "{}", self.to_string_hex())
34    }
35}
36
37/// Zero-sized marker; actual Tower layer integration happens in
38/// [`crate::server::RpcServer::build_router`] where we attach the id to
39/// request extensions before the other layers fire.
40#[derive(Debug, Default, Clone, Copy)]
41pub struct RequestIdLayer;
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    /// **Proves:** two freshly-generated `RequestId`s are distinct (with
48    /// overwhelming probability).
49    ///
50    /// **Why it matters:** The whole point of a request id is uniqueness.
51    /// UUIDv7 gives us both ordering and uniqueness; a regression to a
52    /// constant / counter would break correlation across restarts.
53    ///
54    /// **Catches:** accidental `Uuid::nil()` or `Uuid::from_u128(0)`.
55    #[test]
56    fn requests_are_unique() {
57        let a = RequestId::new();
58        let b = RequestId::new();
59        assert_ne!(a, b);
60    }
61
62    /// **Proves:** `to_string_hex` produces a 32-char lowercase hex
63    /// representation (UUID simple form, no hyphens).
64    ///
65    /// **Why it matters:** Dashboards / log aggregators often index on this
66    /// exact form. Any change to hyphenated / uppercase would break those
67    /// queries.
68    ///
69    /// **Catches:** a regression to `Uuid::to_string()` (hyphenated).
70    #[test]
71    fn to_string_hex_is_simple_form() {
72        let r = RequestId::new();
73        let s = r.to_string_hex();
74        assert_eq!(s.len(), 32);
75        assert!(s
76            .chars()
77            .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()));
78    }
79}