use http::{HeaderValue, Request};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct RequestId(String);
impl RequestId {
pub fn new() -> Self {
Self(Uuid::new_v4().to_string())
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn from_header(header: &HeaderValue) -> Option<Self> {
header.to_str().ok().map(|s| Self(s.to_owned()))
}
pub fn from_request<B>(req: &Request<B>) -> Self {
req.headers()
.get("x-request-id")
.and_then(Self::from_header)
.unwrap_or_default()
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
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 From<String> for RequestId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<RequestId> for String {
fn from(id: RequestId) -> Self {
id.0
}
}
impl FromStr for RequestId {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_owned()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::Request;
#[test]
fn new_generates_valid_uuid() {
let id1 = RequestId::new();
let id2 = RequestId::new();
assert_ne!(id1, id2);
assert!(Uuid::parse_str(id1.as_str()).is_ok());
}
#[test]
fn from_str_creates_request_id() {
let id = "custom-id-123".parse::<RequestId>().unwrap();
assert_eq!(id.as_str(), "custom-id-123");
}
#[test]
fn from_header_extracts_valid_value() {
let header = HeaderValue::from_static("test-request-id");
let id = RequestId::from_header(&header).unwrap();
assert_eq!(id.as_str(), "test-request-id");
}
#[test]
fn from_request_extracts_header() {
let mut req = Request::builder().body(()).unwrap();
req.headers_mut()
.insert("x-request-id", HeaderValue::from_static("header-id"));
let id = RequestId::from_request(&req);
assert_eq!(id.as_str(), "header-id");
}
#[test]
fn from_request_generates_when_missing() {
let req = Request::builder().body(()).unwrap();
let id = RequestId::from_request(&req);
assert!(Uuid::parse_str(id.as_str()).is_ok());
}
#[test]
fn display_implementation() {
let id = "test-id".parse::<RequestId>().unwrap();
assert_eq!(format!("{}", id), "test-id");
}
#[test]
fn string_conversions() {
let original = "test-id".to_string();
let id = RequestId::from(original.clone());
let converted: String = id.into();
assert_eq!(original, converted);
}
}