ddd_toolkit_core/adapter/
id.rs1use crate::domain::EntityId;
2use crate::port::id::IdGenerator;
3
4macro_rules! uuid_id_generator {
5 ($(#[$doc:meta])* $name:ident, $make:path) => {
6 $(#[$doc])*
7 #[derive(Debug, Clone)]
8 pub struct $name<Id>(core::marker::PhantomData<fn() -> Id>);
9
10 impl<Id> $name<Id> {
11 #[doc = concat!("Creates a new `", stringify!($name), "`.")]
12 pub fn new() -> Self {
13 Self(core::marker::PhantomData)
14 }
15 }
16
17 impl<Id> Default for $name<Id> {
18 fn default() -> Self {
19 Self::new()
20 }
21 }
22
23 impl<Id> IdGenerator<Id> for $name<Id>
24 where
25 Id: EntityId + TryFrom<uuid::Uuid, Error = core::convert::Infallible>,
26 {
27 fn generate(&self) -> Id {
28 match Id::try_from($make()) {
29 Ok(id) => id,
30 Err(never) => match never {},
31 }
32 }
33 }
34 };
35}
36
37uuid_id_generator!(
38 UuidV4Generator,
75 uuid::Uuid::new_v4
76);
77
78uuid_id_generator!(
79 UuidV7Generator,
118 uuid::Uuid::now_v7
119);
120
121#[cfg(test)]
122mod test {
123 use std::fmt::Display;
124
125 use uuid::{Uuid, Variant};
126
127 use crate::domain::ValueObject;
128
129 use super::*;
130
131 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
132 struct UuidFooId(Uuid);
133
134 impl ValueObject for UuidFooId {}
135
136 impl Display for UuidFooId {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 write!(f, "{}", self.0)
139 }
140 }
141
142 impl EntityId for UuidFooId {}
143
144 impl From<Uuid> for UuidFooId {
145 fn from(value: Uuid) -> Self {
146 Self(value)
147 }
148 }
149
150 #[test]
151 fn uuid_v4_generate_returns_version_4_rfc4122_uuid() {
152 let generator = UuidV4Generator::<UuidFooId>::new();
153
154 let id = generator.generate();
155
156 assert_eq!(id.0.get_version_num(), 4);
157 assert_eq!(id.0.get_variant(), Variant::RFC4122);
158 }
159
160 #[test]
161 fn uuid_v4_generate_twice_returns_distinct_ids() {
162 let generator = UuidV4Generator::<UuidFooId>::new();
163
164 assert_ne!(generator.generate(), generator.generate());
165 }
166
167 #[test]
168 fn uuid_v4_new_and_default_construct_usable_generator() {
169 let from_new = UuidV4Generator::<UuidFooId>::new();
170 let from_default = UuidV4Generator::<UuidFooId>::default();
171
172 assert_eq!(from_new.generate().0.get_version_num(), 4);
173 assert_eq!(from_default.generate().0.get_version_num(), 4);
174 }
175
176 #[test]
177 fn uuid_v4_generator_works_through_id_generator_bound() {
178 fn next_id<G: IdGenerator<UuidFooId>>(generator: &G) -> UuidFooId {
179 generator.generate()
180 }
181
182 let generator = UuidV4Generator::new();
183
184 assert_eq!(next_id(&generator).0.get_version_num(), 4);
185 }
186
187 #[test]
188 fn uuid_v7_generate_returns_version_7_rfc4122_uuid() {
189 let generator = UuidV7Generator::<UuidFooId>::new();
190
191 let id = generator.generate();
192
193 assert_eq!(id.0.get_version_num(), 7);
194 assert_eq!(id.0.get_variant(), Variant::RFC4122);
195 }
196
197 #[test]
198 fn uuid_v7_generate_twice_returns_distinct_ids() {
199 let generator = UuidV7Generator::<UuidFooId>::new();
200
201 assert_ne!(generator.generate(), generator.generate());
202 }
203
204 #[test]
205 fn uuid_v7_timestamps_are_non_decreasing() {
206 let generator = UuidV7Generator::<UuidFooId>::new();
207
208 let timestamps: Vec<_> = (0..10)
209 .map(|_| {
210 generator
211 .generate()
212 .0
213 .get_timestamp()
214 .expect("v7 uuid should embed a timestamp")
215 .to_unix()
216 })
217 .collect();
218
219 for pair in timestamps.windows(2) {
220 assert!(pair[0] <= pair[1], "timestamps went backwards: {pair:?}");
221 }
222 }
223
224 #[test]
225 fn uuid_generators_are_send_sync_and_clone() {
226 fn assert_impl<T: Send + Sync + Clone>() {}
227
228 assert_impl::<UuidV4Generator<UuidFooId>>();
229 assert_impl::<UuidV7Generator<UuidFooId>>();
230 }
231}