allsource_core/domain/value_objects/
transaction_id.rs1use crate::error::Result;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub struct TransactionId(Uuid);
22
23impl TransactionId {
24 pub fn new() -> Self {
34 Self(Uuid::new_v4())
35 }
36
37 pub fn from_uuid(uuid: Uuid) -> Self {
49 Self(uuid)
50 }
51
52 pub fn parse(value: &str) -> Result<Self> {
65 let uuid = Uuid::parse_str(value).map_err(|e| {
66 crate::error::AllSourceError::InvalidInput(format!(
67 "Invalid transaction ID '{value}': {e}"
68 ))
69 })?;
70 Ok(Self(uuid))
71 }
72
73 pub fn as_uuid(&self) -> Uuid {
75 self.0
76 }
77
78 pub fn is_nil(&self) -> bool {
80 self.0.is_nil()
81 }
82
83 pub fn nil() -> Self {
85 Self(Uuid::nil())
86 }
87}
88
89impl Default for TransactionId {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95impl fmt::Display for TransactionId {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "{}", self.0)
98 }
99}
100
101impl From<Uuid> for TransactionId {
102 fn from(uuid: Uuid) -> Self {
103 Self(uuid)
104 }
105}
106
107impl From<TransactionId> for Uuid {
108 fn from(transaction_id: TransactionId) -> Self {
109 transaction_id.0
110 }
111}
112
113impl TryFrom<&str> for TransactionId {
114 type Error = crate::error::AllSourceError;
115
116 fn try_from(value: &str) -> Result<Self> {
117 TransactionId::parse(value)
118 }
119}
120
121impl TryFrom<String> for TransactionId {
122 type Error = crate::error::AllSourceError;
123
124 fn try_from(value: String) -> Result<Self> {
125 TransactionId::parse(&value)
126 }
127}
128
129impl AsRef<Uuid> for TransactionId {
130 fn as_ref(&self) -> &Uuid {
131 &self.0
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_create_transaction_id() {
141 let transaction_id = TransactionId::new();
142 assert!(!transaction_id.is_nil());
143 }
144
145 #[test]
146 fn test_from_uuid() {
147 let uuid = Uuid::new_v4();
148 let transaction_id = TransactionId::from_uuid(uuid);
149 assert_eq!(transaction_id.as_uuid(), uuid);
150 }
151
152 #[test]
153 fn test_parse_valid_uuid() {
154 let uuid_str = "550e8400-e29b-41d4-a716-446655440000";
155 let transaction_id = TransactionId::parse(uuid_str);
156 assert!(transaction_id.is_ok());
157 assert_eq!(transaction_id.unwrap().to_string(), uuid_str);
158 }
159
160 #[test]
161 fn test_parse_invalid_uuid() {
162 let invalid = "not-a-uuid";
163 let result = TransactionId::parse(invalid);
164 assert!(result.is_err());
165 }
166
167 #[test]
168 fn test_nil_transaction_id() {
169 let nil_id = TransactionId::nil();
170 assert!(nil_id.is_nil());
171 assert_eq!(nil_id.to_string(), "00000000-0000-0000-0000-000000000000");
172 }
173
174 #[test]
175 fn test_default_creates_new_uuid() {
176 let id1 = TransactionId::default();
177 let id2 = TransactionId::default();
178 assert_ne!(id1, id2);
179 assert!(!id1.is_nil());
180 }
181
182 #[test]
183 fn test_display_trait() {
184 let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
185 let transaction_id = TransactionId::from_uuid(uuid);
186 assert_eq!(
187 format!("{transaction_id}"),
188 "550e8400-e29b-41d4-a716-446655440000"
189 );
190 }
191
192 #[test]
193 fn test_from_uuid_trait() {
194 let uuid = Uuid::new_v4();
195 let transaction_id: TransactionId = uuid.into();
196 assert_eq!(transaction_id.as_uuid(), uuid);
197 }
198
199 #[test]
200 fn test_into_uuid_trait() {
201 let transaction_id = TransactionId::new();
202 let uuid: Uuid = transaction_id.into();
203 assert_eq!(uuid, transaction_id.as_uuid());
204 }
205
206 #[test]
207 fn test_try_from_str() {
208 let transaction_id: Result<TransactionId> =
209 "550e8400-e29b-41d4-a716-446655440000".try_into();
210 assert!(transaction_id.is_ok());
211
212 let invalid: Result<TransactionId> = "invalid".try_into();
213 assert!(invalid.is_err());
214 }
215
216 #[test]
217 fn test_try_from_string() {
218 let transaction_id: Result<TransactionId> = "550e8400-e29b-41d4-a716-446655440000"
219 .to_string()
220 .try_into();
221 assert!(transaction_id.is_ok());
222
223 let invalid: Result<TransactionId> = String::new().try_into();
224 assert!(invalid.is_err());
225 }
226
227 #[test]
228 fn test_equality() {
229 let uuid = Uuid::new_v4();
230 let id1 = TransactionId::from_uuid(uuid);
231 let id2 = TransactionId::from_uuid(uuid);
232 let id3 = TransactionId::new();
233
234 assert_eq!(id1, id2);
235 assert_ne!(id1, id3);
236 }
237
238 #[test]
239 fn test_cloning() {
240 let id1 = TransactionId::new();
241 let id2 = id1; assert_eq!(id1, id2);
243 }
244
245 #[test]
246 fn test_hash_consistency() {
247 use std::collections::HashSet;
248
249 let uuid = Uuid::new_v4();
250 let id1 = TransactionId::from_uuid(uuid);
251 let id2 = TransactionId::from_uuid(uuid);
252
253 let mut set = HashSet::new();
254 set.insert(id1);
255
256 assert!(set.contains(&id2));
257 }
258
259 #[test]
260 fn test_serde_serialization() {
261 let transaction_id = TransactionId::parse("550e8400-e29b-41d4-a716-446655440000").unwrap();
262
263 let json = serde_json::to_string(&transaction_id).unwrap();
265 assert_eq!(json, "\"550e8400-e29b-41d4-a716-446655440000\"");
266
267 let deserialized: TransactionId = serde_json::from_str(&json).unwrap();
269 assert_eq!(deserialized, transaction_id);
270 }
271
272 #[test]
273 fn test_as_ref() {
274 let transaction_id = TransactionId::new();
275 let uuid_ref: &Uuid = transaction_id.as_ref();
276 assert_eq!(*uuid_ref, transaction_id.as_uuid());
277 }
278}