d6dc02f
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<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
match req.extensions().get::<Session>() {
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<String> {
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<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let request_id = req
.extensions()
.get::<RequestId>()
.cloned()
.unwrap_or_else(|| {
RequestId::from_header(
req.headers()
.get(REQUEST_ID_HEADER)
.and_then(|value| value.to_str().ok()),
)
});
ready(Ok(request_id))
}
}