tako_rs_plugins/middleware/
request_id.rs1use std::future::Future;
9use std::pin::Pin;
10use std::sync::Arc;
11
12use http::HeaderName;
13use http::HeaderValue;
14use tako_rs_core::middleware::IntoMiddleware;
15use tako_rs_core::middleware::Next;
16use tako_rs_core::types::Request;
17use tako_rs_core::types::Response;
18
19#[derive(Debug, Clone)]
21pub struct RequestIdValue(pub String);
22
23pub struct RequestId {
38 header: HeaderName,
39 generator: Arc<dyn Fn() -> String + Send + Sync + 'static>,
40}
41
42impl Default for RequestId {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl RequestId {
49 pub fn new() -> Self {
51 Self {
52 header: HeaderName::from_static("x-request-id"),
53 generator: Arc::new(|| uuid::Uuid::new_v4().to_string()),
54 }
55 }
56
57 pub fn header_name(mut self, name: &'static str) -> Self {
59 self.header = HeaderName::from_static(name);
60 self
61 }
62
63 pub fn generator(mut self, f: impl Fn() -> String + Send + Sync + 'static) -> Self {
65 self.generator = Arc::new(f);
66 self
67 }
68}
69
70impl IntoMiddleware for RequestId {
71 fn into_middleware(
72 self,
73 ) -> impl Fn(Request, Next) -> Pin<Box<dyn Future<Output = Response> + Send + 'static>>
74 + Clone
75 + Send
76 + Sync
77 + 'static {
78 let header = self.header;
79 let generator = self.generator;
80
81 move |mut req: Request, next: Next| {
82 let header = header.clone();
83 let generator = generator.clone();
84
85 Box::pin(async move {
86 const MAX_INBOUND_LEN: usize = 256;
94 let id = req
95 .headers()
96 .get(&header)
97 .and_then(|v| v.to_str().ok())
98 .filter(|s| !s.is_empty() && s.len() <= MAX_INBOUND_LEN)
99 .map_or_else(|| generator(), std::string::ToString::to_string);
100
101 req.extensions_mut().insert(RequestIdValue(id.clone()));
103
104 let mut resp = next.run(req).await;
105
106 if let Ok(val) = HeaderValue::from_str(&id) {
108 resp.headers_mut().insert(header, val);
109 }
110
111 resp
112 })
113 }
114 }
115}