1use serde::{Deserialize, Serialize};
9use std::fmt;
10use uuid::Uuid;
11
12macro_rules! id_type {
13 ($name:ident, $prefix:expr) => {
14 #[derive(Clone, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
15 pub struct $name(pub String);
16
17 impl $name {
18 pub fn new() -> Self {
19 Self(format!("{}_{}", $prefix, Uuid::new_v4().simple()))
20 }
21
22 pub fn from_string(s: impl Into<String>) -> Self {
23 Self(s.into())
24 }
25
26 pub fn as_str(&self) -> &str {
27 &self.0
28 }
29 }
30
31 impl fmt::Display for $name {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 f.write_str(&self.0)
34 }
35 }
36
37 impl fmt::Debug for $name {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.write_str(concat!(stringify!($name), "([redacted])"))
43 }
44 }
45
46 impl Default for $name {
47 fn default() -> Self {
48 Self::new()
49 }
50 }
51 };
52}
53
54id_type!(ConversationId, "conv");
55id_type!(SessionId, "sess");
56id_type!(ConnectionId, "conn");
57id_type!(StreamId, "strm");
58id_type!(MessageId, "msg");
59id_type!(ParticipantId, "part");
60id_type!(IdentityId, "id");
61id_type!(DeviceId, "dev");
62id_type!(BridgeId, "brdg");
63id_type!(MediaRouteId, "route");
64id_type!(TenantId, "tnt");
65id_type!(RecordingId, "rec");
66id_type!(ListenerId, "lstn");
67id_type!(AttachmentId, "att");
68id_type!(AiAttachmentId, "ai");
69id_type!(PlaybackId, "play");
70id_type!(TranscriptionId, "trn");
71id_type!(TransferAttemptId, "xfer");
72
73#[cfg(test)]
74mod diagnostic_tests {
75 use super::*;
76
77 #[test]
78 fn identifier_debug_never_discloses_correlation_values() {
79 const CANARY: &str = "id-diagnostic-canary\r\nAuthorization: exposed";
80 let ids = [
81 format!("{:?}", ConversationId::from_string(CANARY)),
82 format!("{:?}", SessionId::from_string(CANARY)),
83 format!("{:?}", ConnectionId::from_string(CANARY)),
84 format!("{:?}", StreamId::from_string(CANARY)),
85 format!("{:?}", MessageId::from_string(CANARY)),
86 format!("{:?}", TenantId::from_string(CANARY)),
87 format!("{:?}", TransferAttemptId::from_string(CANARY)),
88 ];
89 for debug in ids {
90 assert!(!debug.contains(CANARY));
91 assert!(debug.contains("[redacted]"));
92 }
93 assert_eq!(SessionId::from_string(CANARY).to_string(), CANARY);
94 }
95}