pjson_rs_domain/value_objects/
id.rs1use std::fmt;
7use std::marker::PhantomData;
8use uuid::Uuid;
9
10mod private {
12 pub trait Sealed {}
13}
14
15pub trait IdMarker: private::Sealed + Send + Sync + 'static {}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub struct SessionMarker;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub struct StreamMarker;
28
29impl private::Sealed for SessionMarker {}
30impl private::Sealed for StreamMarker {}
31
32impl IdMarker for SessionMarker {}
33impl IdMarker for StreamMarker {}
34
35#[derive(Clone, Copy, PartialEq, Eq, Hash)]
67pub struct Id<T: IdMarker> {
68 value: Uuid,
69 _marker: PhantomData<T>,
70}
71
72impl<T: IdMarker> Id<T> {
73 #[must_use]
75 pub fn new() -> Self {
76 Self {
77 value: Uuid::new_v4(),
78 _marker: PhantomData,
79 }
80 }
81
82 #[must_use]
84 pub fn from_uuid(uuid: Uuid) -> Self {
85 Self {
86 value: uuid,
87 _marker: PhantomData,
88 }
89 }
90
91 pub fn from_string(s: &str) -> Result<Self, uuid::Error> {
97 Uuid::parse_str(s).map(Self::from_uuid)
98 }
99
100 #[must_use]
102 pub fn as_uuid(&self) -> Uuid {
103 self.value
104 }
105
106 #[must_use]
108 pub fn as_str(&self) -> String {
109 self.value.to_string()
110 }
111}
112
113impl<T: IdMarker> Default for Id<T> {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl<T: IdMarker> fmt::Debug for Id<T> {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 f.debug_tuple(std::any::type_name::<Self>())
122 .field(&self.value)
123 .finish()
124 }
125}
126
127impl<T: IdMarker> fmt::Display for Id<T> {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 write!(f, "{}", self.value)
130 }
131}
132
133impl<T: IdMarker> From<Uuid> for Id<T> {
134 fn from(uuid: Uuid) -> Self {
135 Self::from_uuid(uuid)
136 }
137}
138
139impl<T: IdMarker> From<Id<T>> for Uuid {
140 fn from(id: Id<T>) -> Self {
141 id.value
142 }
143}
144
145pub type SessionId = Id<SessionMarker>;
147
148pub type StreamId = Id<StreamMarker>;
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_id_creation() {
157 let id1 = SessionId::new();
158 let id2 = SessionId::new();
159
160 assert_ne!(id1, id2);
161 assert_eq!(id1.as_uuid().get_version_num(), 4);
162 }
163
164 #[test]
165 fn test_id_from_string() {
166 let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
167 let id = SessionId::from_string(uuid_str).unwrap();
168 assert_eq!(id.as_str(), uuid_str);
169 }
170
171 #[test]
172 fn test_id_from_invalid_string() {
173 let result = SessionId::from_string("invalid-uuid");
174 assert!(result.is_err());
175 }
176
177 #[test]
178 fn test_different_id_types_are_distinct() {
179 let session_uuid = Uuid::new_v4();
180 let session_id = SessionId::from_uuid(session_uuid);
181 let stream_id = StreamId::from_uuid(session_uuid);
182
183 assert_eq!(session_id.as_uuid(), stream_id.as_uuid());
185
186 }
188
189 #[test]
190 fn test_id_default() {
191 let id = SessionId::default();
192 assert_eq!(id.as_uuid().get_version_num(), 4);
193 }
194
195 #[test]
196 fn test_id_debug_display() {
197 let id = SessionId::new();
198 let debug_str = format!("{:?}", id);
199 assert!(debug_str.contains("Id<"));
200
201 let display_str = format!("{}", id);
202 assert!(Uuid::parse_str(&display_str).is_ok());
203 }
204
205 #[test]
206 fn test_id_from_uuid_conversion() {
207 let uuid = Uuid::new_v4();
208 let id: SessionId = uuid.into();
209 let back: Uuid = id.into();
210 assert_eq!(uuid, back);
211 }
212
213 #[test]
214 fn test_stream_id() {
215 let id1 = StreamId::new();
216 let id2 = StreamId::new();
217 assert_ne!(id1, id2);
218 }
219}