use std::future::{Ready, ready}; use actix_web::dev::Payload; use actix_web::{FromRequest, HttpMessage, HttpRequest}; use uuid::Uuid; pub const REQUEST_ID_HEADER: &str = "x-request-id"; pub const LEGACY_REQUEST_ID_HEADER: &str = "gitdata-request-id"; pub const REQUEST_ID_MAX_LEN: usize = 64; #[derive(Debug, Clone)] pub struct RequestId(pub String); impl RequestId { pub fn from_header(value: Option<&str>) -> Self { Self( value .and_then(validate_request_id) .unwrap_or_else(new_request_id), ) } pub fn as_str(&self) -> &str { &self.0 } } pub fn validate_request_id(value: &str) -> Option { let value = value.trim(); if value.is_empty() || value.len() > REQUEST_ID_MAX_LEN { return None; } let allowed = value .bytes() .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b':')); allowed.then(|| value.to_owned()) } pub fn new_request_id() -> String { Uuid::now_v7().to_string() } impl FromRequest for RequestId { type Error = actix_web::Error; type Future = Ready>; fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { let request_id = req .extensions() .get::() .cloned() .unwrap_or_else(|| { RequestId::from_header( req.headers() .get(REQUEST_ID_HEADER) .and_then(|value| value.to_str().ok()), ) }); ready(Ok(request_id)) } }