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);
63typed_id!(MemoryId);
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
67#[serde(transparent)]
68pub struct BlobHash(String);
69
70impl BlobHash {
71 pub fn from_hex(hex: impl Into<String>) -> Self {
72 Self(hex.into())
73 }
74
75 pub fn as_str(&self) -> &str {
76 &self.0
77 }
78}
79
80impl fmt::Display for BlobHash {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(f, "{}", self.0)
83 }
84}
85
86impl From<String> for BlobHash {
87 fn from(s: String) -> Self {
88 Self(s)
89 }
90}
91
92impl AsRef<str> for BlobHash {
93 fn as_ref(&self) -> &str {
94 &self.0
95 }
96}
97
98pub type SeqNo = u64;
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn event_id_new_is_unique() {
107 let a = EventId::new();
108 let b = EventId::new();
109 assert_ne!(a, b);
110 }
111
112 #[test]
113 fn session_id_from_string() {
114 let id = SessionId::from_string("test-session");
115 assert_eq!(id.as_str(), "test-session");
116 assert_eq!(id.to_string(), "test-session");
117 }
118
119 #[test]
120 fn branch_id_from_str_trait() {
121 let id: BranchId = "main".into();
122 assert_eq!(id.as_str(), "main");
123 }
124
125 #[test]
126 fn typed_id_display() {
127 let id = RunId::from_string("run-42");
128 assert_eq!(format!("{id}"), "run-42");
129 }
130
131 #[test]
132 fn typed_id_default_generates_ulid() {
133 let id = EventId::default();
134 assert_eq!(id.as_str().len(), 26);
136 }
137
138 #[test]
139 fn typed_id_as_ref_str() {
140 let id = SessionId::from_string("hello");
141 let s: &str = id.as_ref();
142 assert_eq!(s, "hello");
143 }
144
145 #[test]
146 fn typed_id_from_string_owned() {
147 let id = BranchId::from(String::from("dev"));
148 assert_eq!(id.as_str(), "dev");
149 }
150
151 #[test]
152 fn typed_id_serde_roundtrip() {
153 let id = EventId::from_string("01HXYZ");
154 let json = serde_json::to_string(&id).unwrap();
155 assert_eq!(json, "\"01HXYZ\"");
156 let back: EventId = serde_json::from_str(&json).unwrap();
157 assert_eq!(id, back);
158 }
159
160 #[test]
161 fn typed_id_hash_equality() {
162 use std::collections::HashSet;
163 let id = SessionId::from_string("same");
164 let id2 = SessionId::from_string("same");
165 let mut set = HashSet::new();
166 set.insert(id.clone());
167 assert!(set.contains(&id2));
168 }
169
170 #[test]
171 fn blob_hash_from_hex_and_display() {
172 let hash = BlobHash::from_hex("abcdef0123456789");
173 assert_eq!(hash.as_str(), "abcdef0123456789");
174 assert_eq!(format!("{hash}"), "abcdef0123456789");
175 }
176
177 #[test]
178 fn blob_hash_serde_roundtrip() {
179 let hash = BlobHash::from_hex("deadbeef");
180 let json = serde_json::to_string(&hash).unwrap();
181 assert_eq!(json, "\"deadbeef\"");
182 let back: BlobHash = serde_json::from_str(&json).unwrap();
183 assert_eq!(hash, back);
184 }
185
186 #[test]
187 fn blob_hash_from_string_trait() {
188 let hash = BlobHash::from(String::from("cafebabe"));
189 assert_eq!(hash.as_str(), "cafebabe");
190 }
191
192 #[test]
193 fn blob_hash_as_ref() {
194 let hash = BlobHash::from_hex("abc");
195 let s: &str = hash.as_ref();
196 assert_eq!(s, "abc");
197 }
198}