openkind_api/middleware/
request_id.rs1use axum::{
4 body::Body,
5 http::{HeaderName, HeaderValue, Request},
6 middleware::Next,
7 response::Response,
8};
9
10pub const REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("x-typesafe-request-id");
12
13pub const MAX_REQUEST_ID_LEN: usize = 128;
15
16pub fn is_safe_request_id(s: &str) -> bool {
18 !s.is_empty()
19 && s.len() <= MAX_REQUEST_ID_LEN
20 && s.chars()
21 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
22}
23
24#[derive(Debug, Clone)]
26pub struct RequestId(pub String);
27
28fn new_request_id() -> String {
31 let uuid = uuid::Uuid::new_v4();
32 let mut buffer = [0u8; uuid::fmt::Hyphenated::LENGTH];
33 uuid.hyphenated().encode_lower(&mut buffer);
34 std::str::from_utf8(&buffer)
36 .expect("hyphenated UUID is ASCII")
37 .to_owned()
38}
39
40pub async fn request_id_layer(mut req: Request<Body>, next: Next) -> Response {
42 let id = req
45 .headers()
46 .get(&REQUEST_ID_HEADER)
47 .and_then(|v| v.to_str().ok())
48 .filter(|s| is_safe_request_id(s))
49 .map(|s| s.to_string())
50 .unwrap_or_else(new_request_id);
51
52 req.extensions_mut().insert(RequestId(id.clone()));
53
54 let mut resp = next.run(req).await;
55 if let Ok(v) = HeaderValue::from_str(&id) {
56 resp.headers_mut().insert(REQUEST_ID_HEADER.clone(), v);
57 }
58 resp
59}