Skip to main content

ddd_toolkit_core/adapter/
id.rs

1use 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    /// Generates ids from a random (v4) UUID.
39    UuidV4Generator,
40    uuid::Uuid::new_v4
41);
42
43uuid_id_generator!(
44    /// Generates ids from a time-ordered (v7) UUID.
45    UuidV7Generator,
46    uuid::Uuid::now_v7
47);
48
49#[cfg(test)]
50mod test {
51    use std::fmt::Display;
52
53    use uuid::{Uuid, Variant};
54
55    use crate::domain::ValueObject;
56
57    use super::*;
58
59    #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
60    struct UuidFooId(Uuid);
61
62    impl ValueObject for UuidFooId {}
63
64    impl Display for UuidFooId {
65        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66            write!(f, "{}", self.0)
67        }
68    }
69
70    impl EntityId for UuidFooId {}
71
72    impl From<Uuid> for UuidFooId {
73        fn from(value: Uuid) -> Self {
74            Self(value)
75        }
76    }
77
78    #[test]
79    fn uuid_v4_generate_returns_version_4_rfc4122_uuid() {
80        let generator = UuidV4Generator::<UuidFooId>::new();
81
82        let id = generator.generate();
83
84        assert_eq!(id.0.get_version_num(), 4);
85        assert_eq!(id.0.get_variant(), Variant::RFC4122);
86    }
87
88    #[test]
89    fn uuid_v4_generate_twice_returns_distinct_ids() {
90        let generator = UuidV4Generator::<UuidFooId>::new();
91
92        assert_ne!(generator.generate(), generator.generate());
93    }
94
95    #[test]
96    fn uuid_v4_new_and_default_construct_usable_generator() {
97        let from_new = UuidV4Generator::<UuidFooId>::new();
98        let from_default = UuidV4Generator::<UuidFooId>::default();
99
100        assert_eq!(from_new.generate().0.get_version_num(), 4);
101        assert_eq!(from_default.generate().0.get_version_num(), 4);
102    }
103
104    #[test]
105    fn uuid_v4_generator_works_through_id_generator_bound() {
106        fn next_id<G: IdGenerator<UuidFooId>>(generator: &G) -> UuidFooId {
107            generator.generate()
108        }
109
110        let generator = UuidV4Generator::new();
111
112        assert_eq!(next_id(&generator).0.get_version_num(), 4);
113    }
114
115    #[test]
116    fn uuid_v7_generate_returns_version_7_rfc4122_uuid() {
117        let generator = UuidV7Generator::<UuidFooId>::new();
118
119        let id = generator.generate();
120
121        assert_eq!(id.0.get_version_num(), 7);
122        assert_eq!(id.0.get_variant(), Variant::RFC4122);
123    }
124
125    #[test]
126    fn uuid_v7_generate_twice_returns_distinct_ids() {
127        let generator = UuidV7Generator::<UuidFooId>::new();
128
129        assert_ne!(generator.generate(), generator.generate());
130    }
131
132    #[test]
133    fn uuid_v7_timestamps_are_non_decreasing() {
134        let generator = UuidV7Generator::<UuidFooId>::new();
135
136        let timestamps: Vec<_> = (0..10)
137            .map(|_| {
138                generator
139                    .generate()
140                    .0
141                    .get_timestamp()
142                    .expect("v7 uuid should embed a timestamp")
143                    .to_unix()
144            })
145            .collect();
146
147        for pair in timestamps.windows(2) {
148            assert!(pair[0] <= pair[1], "timestamps went backwards: {pair:?}");
149        }
150    }
151
152    #[test]
153    fn uuid_generators_are_send_sync_and_clone() {
154        fn assert_impl<T: Send + Sync + Clone>() {}
155
156        assert_impl::<UuidV4Generator<UuidFooId>>();
157        assert_impl::<UuidV7Generator<UuidFooId>>();
158    }
159}