made_core/value_objects/
trace_id.rs1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::DomainError;
6
7const TRACE_ID_HEX_LEN: usize = 32;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct TraceId(String);
13
14impl TraceId {
15 pub fn new(raw: impl Into<String>) -> Result<Self, DomainError> {
17 let raw = raw.into().to_ascii_lowercase();
18 if raw.len() != TRACE_ID_HEX_LEN {
19 return Err(DomainError::FieldTooLong {
20 field: "trace_id",
21 actual: raw.len(),
22 max: TRACE_ID_HEX_LEN,
23 });
24 }
25 if !raw.bytes().all(|byte| byte.is_ascii_hexdigit()) {
26 return Err(DomainError::InvalidCharacters { field: "trace_id" });
27 }
28 if raw.bytes().all(|byte| byte == b'0') {
29 return Err(DomainError::InvariantViolated {
30 reason: "trace_id must not be all zeros",
31 });
32 }
33 Ok(Self(raw))
34 }
35
36 #[must_use]
37 pub fn as_str(&self) -> &str {
38 &self.0
39 }
40}
41
42impl fmt::Display for TraceId {
43 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44 formatter.write_str(self.as_str())
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn accepts_and_canonicalises_a_w3c_trace_id() {
54 let id = TraceId::new("0AF7651916CD43DD8448EB211C80319C").unwrap();
55 assert_eq!(id.as_str(), "0af7651916cd43dd8448eb211c80319c");
56 }
57
58 #[test]
59 fn rejects_invalid_and_zero_identities() {
60 assert!(TraceId::new("short").is_err());
61 assert!(TraceId::new("0af7651916cd43dd8448eb211c80319z").is_err());
62 assert!(TraceId::new("00000000000000000000000000000000").is_err());
63 }
64}