tower_http_cache/
request_id.rs1use http::{HeaderValue, Request};
8use serde::{Deserialize, Serialize};
9use std::fmt;
10use std::str::FromStr;
11use uuid::Uuid;
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
19pub struct RequestId(String);
20
21impl RequestId {
22 pub fn new() -> Self {
24 Self(Uuid::new_v4().to_string())
25 }
26
27 pub fn from_string(s: impl Into<String>) -> Self {
32 Self(s.into())
33 }
34
35 pub fn from_header(header: &HeaderValue) -> Option<Self> {
39 header.to_str().ok().map(|s| Self(s.to_owned()))
40 }
41
42 pub fn from_request<B>(req: &Request<B>) -> Self {
47 req.headers()
48 .get("x-request-id")
49 .and_then(Self::from_header)
50 .unwrap_or_default()
51 }
52
53 pub fn as_str(&self) -> &str {
55 &self.0
56 }
57
58 pub fn into_string(self) -> String {
60 self.0
61 }
62}
63
64impl Default for RequestId {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl fmt::Display for RequestId {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "{}", self.0)
73 }
74}
75
76impl From<String> for RequestId {
77 fn from(s: String) -> Self {
78 Self(s)
79 }
80}
81
82impl From<RequestId> for String {
83 fn from(id: RequestId) -> Self {
84 id.0
85 }
86}
87
88impl FromStr for RequestId {
89 type Err = std::convert::Infallible;
90
91 fn from_str(s: &str) -> Result<Self, Self::Err> {
92 Ok(Self(s.to_owned()))
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use http::Request;
100
101 #[test]
102 fn new_generates_valid_uuid() {
103 let id1 = RequestId::new();
104 let id2 = RequestId::new();
105 assert_ne!(id1, id2);
106 assert!(Uuid::parse_str(id1.as_str()).is_ok());
107 }
108
109 #[test]
110 fn from_str_creates_request_id() {
111 let id = "custom-id-123".parse::<RequestId>().unwrap();
112 assert_eq!(id.as_str(), "custom-id-123");
113 }
114
115 #[test]
116 fn from_header_extracts_valid_value() {
117 let header = HeaderValue::from_static("test-request-id");
118 let id = RequestId::from_header(&header).unwrap();
119 assert_eq!(id.as_str(), "test-request-id");
120 }
121
122 #[test]
123 fn from_request_extracts_header() {
124 let mut req = Request::builder().body(()).unwrap();
125 req.headers_mut()
126 .insert("x-request-id", HeaderValue::from_static("header-id"));
127
128 let id = RequestId::from_request(&req);
129 assert_eq!(id.as_str(), "header-id");
130 }
131
132 #[test]
133 fn from_request_generates_when_missing() {
134 let req = Request::builder().body(()).unwrap();
135 let id = RequestId::from_request(&req);
136 assert!(Uuid::parse_str(id.as_str()).is_ok());
137 }
138
139 #[test]
140 fn display_implementation() {
141 let id = "test-id".parse::<RequestId>().unwrap();
142 assert_eq!(format!("{}", id), "test-id");
143 }
144
145 #[test]
146 fn string_conversions() {
147 let original = "test-id".to_string();
148 let id = RequestId::from(original.clone());
149 let converted: String = id.into();
150 assert_eq!(original, converted);
151 }
152}