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}