use axum::{
body::Body,
http::{header::HeaderName, HeaderValue, Request},
middleware::Next,
response::Response,
};
use tracing::{info_span, Instrument};
use uuid::Uuid;
pub static REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("x-request-id");
#[derive(Clone, Debug)]
pub struct RequestId(pub String);
impl std::ops::Deref for RequestId {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub async fn request_id_middleware(mut request: Request<Body>, next: Next) -> Response {
let request_id = request
.headers()
.get(&REQUEST_ID_HEADER)
.and_then(|v| v.to_str().ok())
.map(String::from)
.unwrap_or_else(|| Uuid::new_v4().to_string());
request
.extensions_mut()
.insert(RequestId(request_id.clone()));
let span = info_span!(
"request",
request_id = %request_id,
method = %request.method(),
uri = %request.uri().path(),
);
let mut response = next.run(request).instrument(span).await;
if let Ok(header_value) = HeaderValue::from_str(&request_id) {
response
.headers_mut()
.insert(&REQUEST_ID_HEADER, header_value);
}
response
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_id_deref() {
let id = RequestId("test-123".to_string());
assert_eq!(&*id, "test-123");
}
#[test]
fn test_request_id_clone() {
let id = RequestId("test-123".to_string());
let cloned = id.clone();
assert_eq!(id.0, cloned.0);
}
}