1use serde::{Deserialize, Serialize};
2use std::fmt;
3use ulid::Ulid;
4
5macro_rules! typed_id {
6 ($name:ident) => {
7 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8 #[serde(transparent)]
9 pub struct $name(String);
10
11 impl $name {
12 pub fn new() -> Self {
13 Self(Ulid::new().to_string())
14 }
15
16 pub fn from_string(s: impl Into<String>) -> Self {
17 Self(s.into())
18 }
19
20 pub fn as_str(&self) -> &str {
21 &self.0
22 }
23 }
24
25 impl Default for $name {
26 fn default() -> Self {
27 Self::new()
28 }
29 }
30
31 impl fmt::Display for $name {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 write!(f, "{}", self.0)
34 }
35 }
36
37 impl From<String> for $name {
38 fn from(s: String) -> Self {
39 Self(s)
40 }
41 }
42
43 impl From<&str> for $name {
44 fn from(s: &str) -> Self {
45 Self(s.to_string())
46 }
47 }
48
49 impl AsRef<str> for $name {
50 fn as_ref(&self) -> &str {
51 &self.0
52 }
53 }
54 };
55}
56
57typed_id!(EventId);
58typed_id!(SessionId);
59typed_id!(BranchId);
60typed_id!(RunId);
61typed_id!(SnapshotId);
62typed_id!(ApprovalId);
63
64#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
66#[serde(transparent)]
67pub struct BlobHash(String);
68
69impl BlobHash {
70 pub fn from_hex(hex: impl Into<String>) -> Self {
71 Self(hex.into())
72 }
73
74 pub fn as_str(&self) -> &str {
75 &self.0
76 }
77}
78
79impl fmt::Display for BlobHash {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 write!(f, "{}", self.0)
82 }
83}
84
85impl From<String> for BlobHash {
86 fn from(s: String) -> Self {
87 Self(s)
88 }
89}
90
91impl AsRef<str> for BlobHash {
92 fn as_ref(&self) -> &str {
93 &self.0
94 }
95}
96
97pub type SeqNo = u64;
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn event_id_new_is_unique() {
106 let a = EventId::new();
107 let b = EventId::new();
108 assert_ne!(a, b);
109 }
110
111 #[test]
112 fn session_id_from_string() {
113 let id = SessionId::from_string("test-session");
114 assert_eq!(id.as_str(), "test-session");
115 assert_eq!(id.to_string(), "test-session");
116 }
117
118 #[test]
119 fn branch_id_from_str_trait() {
120 let id: BranchId = "main".into();
121 assert_eq!(id.as_str(), "main");
122 }
123
124 #[test]
125 fn typed_id_display() {
126 let id = RunId::from_string("run-42");
127 assert_eq!(format!("{id}"), "run-42");
128 }
129
130 #[test]
131 fn typed_id_default_generates_ulid() {
132 let id = EventId::default();
133 assert_eq!(id.as_str().len(), 26);
135 }
136
137 #[test]
138 fn typed_id_as_ref_str() {
139 let id = SessionId::from_string("hello");
140 let s: &str = id.as_ref();
141 assert_eq!(s, "hello");
142 }
143
144 #[test]
145 fn typed_id_from_string_owned() {
146 let id = BranchId::from(String::from("dev"));
147 assert_eq!(id.as_str(), "dev");
148 }
149
150 #[test]
151 fn typed_id_serde_roundtrip() {
152 let id = EventId::from_string("01HXYZ");
153 let json = serde_json::to_string(&id).unwrap();
154 assert_eq!(json, "\"01HXYZ\"");
155 let back: EventId = serde_json::from_str(&json).unwrap();
156 assert_eq!(id, back);
157 }
158
159 #[test]
160 fn typed_id_hash_equality() {
161 use std::collections::HashSet;
162 let id = SessionId::from_string("same");
163 let id2 = SessionId::from_string("same");
164 let mut set = HashSet::new();
165 set.insert(id.clone());
166 assert!(set.contains(&id2));
167 }
168
169 #[test]
170 fn blob_hash_from_hex_and_display() {
171 let hash = BlobHash::from_hex("abcdef0123456789");
172 assert_eq!(hash.as_str(), "abcdef0123456789");
173 assert_eq!(format!("{hash}"), "abcdef0123456789");
174 }
175
176 #[test]
177 fn blob_hash_serde_roundtrip() {
178 let hash = BlobHash::from_hex("deadbeef");
179 let json = serde_json::to_string(&hash).unwrap();
180 assert_eq!(json, "\"deadbeef\"");
181 let back: BlobHash = serde_json::from_str(&json).unwrap();
182 assert_eq!(hash, back);
183 }
184
185 #[test]
186 fn blob_hash_from_string_trait() {
187 let hash = BlobHash::from(String::from("cafebabe"));
188 assert_eq!(hash.as_str(), "cafebabe");
189 }
190
191 #[test]
192 fn blob_hash_as_ref() {
193 let hash = BlobHash::from_hex("abc");
194 let s: &str = hash.as_ref();
195 assert_eq!(s, "abc");
196 }
197}