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