use http::Request;
use mti::prelude::*;
use std::fmt;
use std::str::FromStr;
use tower_http::request_id::{MakeRequestId, RequestId as TowerRequestId};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RequestId(MagicTypeId);
impl RequestId {
pub const PREFIX: &'static str = "req";
#[must_use]
pub fn new() -> Self {
Self(Self::PREFIX.create_type_id::<V7>())
}
#[must_use]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[must_use]
pub fn prefix(&self) -> &str {
self.0.prefix().as_str()
}
#[must_use]
pub fn inner(&self) -> &MagicTypeId {
&self.0
}
#[must_use]
pub fn into_inner(self) -> MagicTypeId {
self.0
}
}
impl Default for RequestId {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for RequestId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for RequestId {
type Err = RequestIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mti = MagicTypeId::from_str(s).map_err(RequestIdError::Parse)?;
if mti.prefix().as_str() != Self::PREFIX {
return Err(RequestIdError::InvalidPrefix {
expected: Self::PREFIX.to_string(),
actual: mti.prefix().as_str().to_string(),
});
}
Ok(Self(mti))
}
}
impl AsRef<str> for RequestId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl From<RequestId> for String {
fn from(id: RequestId) -> Self {
id.0.to_string()
}
}
#[derive(Debug, thiserror::Error)]
pub enum RequestIdError {
#[error("failed to parse request ID: {0}")]
Parse(#[from] MagicTypeIdError),
#[error("invalid prefix: expected '{expected}', got '{actual}'")]
InvalidPrefix {
expected: String,
actual: String,
},
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MakeTypedRequestId;
impl MakeRequestId for MakeTypedRequestId {
fn make_request_id<B>(&mut self, _request: &Request<B>) -> Option<TowerRequestId> {
let id = RequestId::new();
let header_value = http::HeaderValue::from_str(id.as_str()).ok()?;
Some(TowerRequestId::new(header_value))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_id_new() {
let id = RequestId::new();
assert!(id.as_str().starts_with("req_"));
assert_eq!(id.prefix(), "req");
assert_eq!(id.as_str().len(), 30);
}
#[test]
fn test_request_id_parse() {
let id_str = "req_01h455vb4pex5vsknk084sn02q";
let id = RequestId::from_str(id_str).unwrap();
assert_eq!(id.as_str(), id_str);
assert_eq!(id.prefix(), "req");
}
#[test]
fn test_request_id_invalid_prefix() {
let id_str = "user_01h455vb4pex5vsknk084sn02q";
let result = RequestId::from_str(id_str);
assert!(result.is_err());
match result.unwrap_err() {
RequestIdError::InvalidPrefix { expected, actual } => {
assert_eq!(expected, "req");
assert_eq!(actual, "user");
}
_ => panic!("Expected InvalidPrefix error"),
}
}
#[test]
fn test_request_id_invalid_format() {
let id_str = "req_invalid";
let result = RequestId::from_str(id_str);
assert!(result.is_err());
}
#[test]
fn test_request_id_display() {
let id = RequestId::new();
let displayed = format!("{}", id);
assert!(displayed.starts_with("req_"));
}
#[test]
fn test_request_id_ordering() {
let id1 = RequestId::new();
std::thread::sleep(std::time::Duration::from_millis(10));
let id2 = RequestId::new();
assert!(id1 < id2);
}
#[test]
fn test_make_typed_request_id() {
let mut maker = MakeTypedRequestId;
let request = http::Request::builder().body(()).unwrap();
let id = maker.make_request_id(&request);
assert!(id.is_some());
let header_value = id.unwrap().into_header_value();
let id_str = header_value.to_str().unwrap();
assert!(id_str.starts_with("req_"));
}
}