use std::future::{Ready, ready}; use actix_web::dev::Payload; use actix_web::{FromRequest, HttpMessage, HttpRequest}; use crate::state::Session; pub const REQUEST_ID_HEADER: &str = "x-request-id"; pub const REQUEST_ID_MAX_LEN: usize = 64; impl FromRequest for Session { type Error = actix_web::Error; type Future = Ready>; fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future { match req.extensions().get::() { Some(session) => ready(Ok(session.clone())), None => ready(Err(actix_web::error::ErrorInternalServerError( "session is not initialized", ))), } } } pub fn validate_request_id(value: Option<&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()) } #[derive(Debug, Clone)] pub struct RequestId(pub String); impl RequestId { pub fn from_header(value: Option<&str>) -> Self { Self(validate_request_id(value).unwrap_or_else(|| uuid::Uuid::now_v7().to_string())) } pub fn as_str(&self) -> &str { &self.0 } } 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)) } }