pavex/telemetry/
server_request_id.rs

1use crate::http::HeaderValue;
2use pavex_macros::methods;
3use std::fmt::Formatter;
4use std::hash::{Hash, Hasher};
5use type_safe_id::{StaticType, TypeSafeId};
6use uuid::Uuid;
7
8/// A unique identifier generated for each incoming request.
9///
10/// It is a [TypeId](https://github.com/jetify-com/typeid).
11/// The prefix is always `sri`, followed by an underscore and UUIDv7 in base32 encoding.
12/// For example:
13///
14/// ```text
15///  sri_2x4y6z8a0b1c2d3e4f5g6h7j8k
16///  └─┘ └────────────────────────┘
17/// prefix    uuid suffix (base32)
18/// ```
19///
20/// # Example
21///
22/// ```rust
23/// use http::{HeaderName, HeaderValue};
24/// use pavex::Response;
25/// use pavex::telemetry::ServerRequestId;
26///
27/// pub async fn request_handler(request_id: ServerRequestId) -> Response {
28///     // Add the request id as a header on the outgoing response
29///     Response::ok()
30///         .insert_header(
31///             HeaderName::from_static("X-Request-Id"),
32///             request_id.header_value()
33///         )
34/// }
35/// ```
36#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
37pub struct ServerRequestId(TypeSafeId<SriTag>);
38
39impl Hash for ServerRequestId {
40    fn hash<H: Hasher>(&self, state: &mut H) {
41        self.0.uuid().hash(state)
42    }
43}
44
45#[derive(Default, Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
46struct SriTag;
47
48impl StaticType for SriTag {
49    const TYPE: &'static str = "sri";
50}
51
52#[methods]
53impl ServerRequestId {
54    /// The default constructor for [`ServerRequestId`].
55    ///
56    /// It generates a new request id using a [UUID v7](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#name-uuid-version-7),
57    /// with a random number and the current (UNIX) timestamp.
58    #[request_scoped(pavex = crate, clone_if_necessary)]
59    pub fn generate() -> Self {
60        Self(TypeSafeId::from_uuid(Uuid::now_v7()))
61    }
62
63    /// Access the underlying UUID.
64    pub fn inner(&self) -> Uuid {
65        self.0.uuid()
66    }
67
68    /// Return a [hyphenated representation](https://docs.rs/uuid/1.7.0/uuid/struct.Uuid.html#formatting)
69    /// of [`ServerRequestId`] to be used as a [`HeaderValue`].
70    pub fn header_value(&self) -> HeaderValue {
71        // It is always a valid header value.
72        self.0.to_string().try_into().unwrap()
73    }
74}
75
76impl From<Uuid> for ServerRequestId {
77    fn from(value: Uuid) -> Self {
78        Self(TypeSafeId::from_uuid(value))
79    }
80}
81
82impl std::fmt::Display for ServerRequestId {
83    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
84        write!(f, "{}", &self.0)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn server_request_id() {
94        let request_id = ServerRequestId::generate();
95        let header_value = request_id.header_value();
96        let header_value = header_value.to_str().unwrap();
97        assert!(header_value.starts_with("sri_"));
98        // Prefix + `_` + UUID in base32 encoding
99        assert_eq!(header_value.len(), 3 + 1 + 26)
100    }
101}