/rust/registry/src/index.crates.io-1949cf8c6b5b557f/uuid-1.18.1/src/v7.rs
Line | Count | Source |
1 | | //! The implementation for Version 7 UUIDs. |
2 | | //! |
3 | | //! Note that you need to enable the `v7` Cargo feature |
4 | | //! in order to use this module. |
5 | | |
6 | | use crate::{rng, std::convert::TryInto, timestamp::Timestamp, Builder, Uuid}; |
7 | | |
8 | | impl Uuid { |
9 | | /// Create a new version 7 UUID using the current time value. |
10 | | /// |
11 | | /// This method is a convenient alternative to [`Uuid::new_v7`] that uses the current system time |
12 | | /// as the source timestamp. All UUIDs generated through this method by the same process are |
13 | | /// guaranteed to be ordered by their creation. |
14 | | #[cfg(feature = "std")] |
15 | 290 | pub fn now_v7() -> Self { |
16 | 290 | Self::new_v7(Timestamp::now( |
17 | 290 | crate::timestamp::context::shared_context_v7(), |
18 | | )) |
19 | 290 | } |
20 | | |
21 | | /// Create a new version 7 UUID using a time value and random bytes. |
22 | | /// |
23 | | /// When the `std` feature is enabled, you can also use [`Uuid::now_v7`]. |
24 | | /// |
25 | | /// Note that usage of this method requires the `v7` feature of this crate |
26 | | /// to be enabled. |
27 | | /// |
28 | | /// Also see [`Uuid::now_v7`] for a convenient way to generate version 7 |
29 | | /// UUIDs using the current system time. |
30 | | /// |
31 | | /// # Examples |
32 | | /// |
33 | | /// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit |
34 | | /// random number. When supplied as such, the data will be combined |
35 | | /// to ensure uniqueness and sortability at millisecond granularity. |
36 | | /// |
37 | | /// ```rust |
38 | | /// # use uuid::{Uuid, Timestamp, NoContext}; |
39 | | /// let ts = Timestamp::from_unix(NoContext, 1497624119, 1234); |
40 | | /// |
41 | | /// let uuid = Uuid::new_v7(ts); |
42 | | /// |
43 | | /// assert!( |
44 | | /// uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7") |
45 | | /// ); |
46 | | /// ``` |
47 | | /// |
48 | | /// A v7 UUID can also be created with a counter to ensure batches of |
49 | | /// UUIDs created together remain sortable: |
50 | | /// |
51 | | /// ```rust |
52 | | /// # use uuid::{Uuid, Timestamp, ContextV7}; |
53 | | /// let context = ContextV7::new(); |
54 | | /// let uuid1 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234)); |
55 | | /// let uuid2 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234)); |
56 | | /// |
57 | | /// assert!(uuid1 < uuid2); |
58 | | /// ``` |
59 | | /// |
60 | | /// # References |
61 | | /// |
62 | | /// * [UUID Version 7 in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-5.7) |
63 | 290 | pub fn new_v7(ts: Timestamp) -> Self { |
64 | 290 | let (secs, nanos) = ts.to_unix(); |
65 | 290 | let millis = (secs * 1000).saturating_add(nanos as u64 / 1_000_000); |
66 | | |
67 | 290 | let mut counter_and_random = rng::u128(); |
68 | | |
69 | 290 | let (mut counter, counter_bits) = ts.counter(); |
70 | | |
71 | 290 | debug_assert!(counter_bits <= 128); |
72 | | |
73 | 290 | let mut counter_bits = counter_bits as u32; |
74 | | |
75 | | // If the counter intersects the variant field then shift around it. |
76 | | // This ensures that any bits set in the counter that would intersect |
77 | | // the variant are still preserved |
78 | 290 | if counter_bits > 12 { |
79 | 290 | let mask = u128::MAX << (counter_bits - 12); |
80 | 290 | |
81 | 290 | counter = (counter & !mask) | ((counter & mask) << 2); |
82 | 290 | |
83 | 290 | counter_bits += 2; |
84 | 290 | } |
85 | | |
86 | 290 | counter_and_random &= u128::MAX.overflowing_shr(counter_bits).0; |
87 | 290 | counter_and_random |= counter |
88 | 290 | .overflowing_shl(128u32.saturating_sub(counter_bits)) |
89 | 290 | .0; |
90 | | |
91 | 290 | Builder::from_unix_timestamp_millis( |
92 | 290 | millis, |
93 | 290 | &counter_and_random.to_be_bytes()[..10].try_into().unwrap(), |
94 | | ) |
95 | 290 | .into_uuid() |
96 | 290 | } |
97 | | } |
98 | | |
99 | | #[cfg(test)] |
100 | | mod tests { |
101 | | use super::*; |
102 | | |
103 | | use crate::{std::string::ToString, ClockSequence, NoContext, Variant, Version}; |
104 | | |
105 | | #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] |
106 | | use wasm_bindgen_test::*; |
107 | | |
108 | | #[test] |
109 | | #[cfg_attr( |
110 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
111 | | wasm_bindgen_test |
112 | | )] |
113 | | fn test_new() { |
114 | | let ts: u64 = 1645557742000; |
115 | | |
116 | | let seconds = ts / 1000; |
117 | | let nanos = ((ts % 1000) * 1_000_000) as u32; |
118 | | |
119 | | let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, seconds, nanos)); |
120 | | let uustr = uuid.hyphenated().to_string(); |
121 | | |
122 | | assert_eq!(uuid.get_version(), Some(Version::SortRand)); |
123 | | assert_eq!(uuid.get_variant(), Variant::RFC4122); |
124 | | assert!(uuid.hyphenated().to_string().starts_with("017f22e2-79b0-7")); |
125 | | |
126 | | // Ensure parsing the same UUID produces the same timestamp |
127 | | let parsed = Uuid::parse_str(uustr.as_str()).unwrap(); |
128 | | |
129 | | assert_eq!(uuid, parsed); |
130 | | } |
131 | | |
132 | | #[test] |
133 | | #[cfg_attr( |
134 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
135 | | wasm_bindgen_test |
136 | | )] |
137 | | #[cfg(feature = "std")] |
138 | | fn test_now() { |
139 | | let uuid = Uuid::now_v7(); |
140 | | |
141 | | assert_eq!(uuid.get_version(), Some(Version::SortRand)); |
142 | | assert_eq!(uuid.get_variant(), Variant::RFC4122); |
143 | | } |
144 | | |
145 | | #[test] |
146 | | #[cfg_attr( |
147 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
148 | | wasm_bindgen_test |
149 | | )] |
150 | | fn test_sorting() { |
151 | | let time1: u64 = 1_496_854_535; |
152 | | let time_fraction1: u32 = 812_000_000; |
153 | | |
154 | | let time2 = time1 + 4000; |
155 | | let time_fraction2 = time_fraction1; |
156 | | |
157 | | let uuid1 = Uuid::new_v7(Timestamp::from_unix(NoContext, time1, time_fraction1)); |
158 | | let uuid2 = Uuid::new_v7(Timestamp::from_unix(NoContext, time2, time_fraction2)); |
159 | | |
160 | | assert!(uuid1.as_bytes() < uuid2.as_bytes()); |
161 | | assert!(uuid1.to_string() < uuid2.to_string()); |
162 | | } |
163 | | |
164 | | #[test] |
165 | | #[cfg_attr( |
166 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
167 | | wasm_bindgen_test |
168 | | )] |
169 | | fn test_new_timestamp_roundtrip() { |
170 | | let time: u64 = 1_496_854_535; |
171 | | let time_fraction: u32 = 812_000_000; |
172 | | |
173 | | let ts = Timestamp::from_unix(NoContext, time, time_fraction); |
174 | | |
175 | | let uuid = Uuid::new_v7(ts); |
176 | | |
177 | | let decoded_ts = uuid.get_timestamp().unwrap(); |
178 | | |
179 | | assert_eq!(ts.to_unix(), decoded_ts.to_unix()); |
180 | | } |
181 | | |
182 | | #[test] |
183 | | #[cfg_attr( |
184 | | all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")), |
185 | | wasm_bindgen_test |
186 | | )] |
187 | | fn test_new_max_context() { |
188 | | struct MaxContext; |
189 | | |
190 | | #[cfg(test)] |
191 | | impl ClockSequence for MaxContext { |
192 | | type Output = u128; |
193 | | |
194 | | fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { |
195 | | u128::MAX |
196 | | } |
197 | | |
198 | | fn usable_bits(&self) -> usize { |
199 | | 128 |
200 | | } |
201 | | } |
202 | | |
203 | | let time: u64 = 1_496_854_535; |
204 | | let time_fraction: u32 = 812_000_000; |
205 | | |
206 | | // Ensure we don't overflow here |
207 | | let ts = Timestamp::from_unix(MaxContext, time, time_fraction); |
208 | | |
209 | | let uuid = Uuid::new_v7(ts); |
210 | | |
211 | | assert_eq!(uuid.get_version(), Some(Version::SortRand)); |
212 | | assert_eq!(uuid.get_variant(), Variant::RFC4122); |
213 | | |
214 | | let decoded_ts = uuid.get_timestamp().unwrap(); |
215 | | |
216 | | assert_eq!(ts.to_unix(), decoded_ts.to_unix()); |
217 | | } |
218 | | } |