typed_uuid/
lib.rs

1//! `Id` is a typed wrapper around a `uuid::Uuid`.
2//!
3//! Use it to add type safety and prevent confusion between different kinds of Uuid.
4//!
5//! # Example
6//! Represent different types of Id to prevent mixups or invalid states. If describing
7//! a unique resource's relationship to another, for example the `Role` a `User` has,
8//! the relationship can be expressed as follows:
9//! ```rust
10//! # #[cfg(feature = "v4")]
11//! # mod submodule {
12//! # struct User;
13//! # struct Role;
14//! // Subtype the Id type to specify the version of the Id, instead
15//! // of repeating yourself everywhere.
16//! type Id<T> = typed_uuid::Id<T, typed_uuid::V4>;
17//!
18//! struct Relation {
19//!     user: Id<User>,
20//!     role: Id<Role>,
21//! }
22//! # }
23//! ```
24//! `Id`s with different `T` parameter types are incompatible, and cannot be compared.
25//!
26//! Attempting to assign an `Id<User>` to a variable of type `Id<Role>` is a compilation error.
27//! ```rust,compile_fail
28//! # mod submodule {
29//! # struct User;
30//! # struct Role;
31//! # type Id<T> = typed_uuid::Id<T, typed_uuid::V4>;
32//! # fn do_thing() {
33//! let user = Id::<User>::new();
34//! let role = Id::<Role>::new();
35//!
36//! // Compilation fails here, can't compare Id<User> and Id<Role>
37//! assert_eq!(user, role);
38//! # }
39//! # }
40//! ```
41//!
42//! But `Id`s of the same type work:
43//! ```rust
44//! # #[cfg(feature = "v4")]
45//! # mod submodule {
46//! # struct User;
47//! # type Id<T> = typed_uuid::Id<T, typed_uuid::V4>;
48//! # fn do_thing() {
49//! let mut first = Id::<User>::new();
50//! let second = Id::<User>::new();
51//!
52//! assert_ne!(first, second);
53//! first = second;
54//! assert_eq!(first, second);
55//! # }
56//! # }
57//! ```
58//! # Usage
59//! When depending on this library, you need to explicitly select the versions of the uuid, you will be using, as well as optionally `serde` support:
60//! ```toml
61//! [dependencies.typed-uuid]
62//! version = "*"
63//! default-features = false
64//! features = ["v4", "serde"]
65//! ```
66#![no_std]
67#![deny(
68    bad_style,
69    dead_code,
70    improper_ctypes,
71    non_shorthand_field_patterns,
72    no_mangle_generic_items,
73    overflowing_literals,
74    path_statements,
75    patterns_in_fns_without_body,
76    private_in_public,
77    unconditional_recursion,
78    unused,
79    unused_allocation,
80    unused_comparisons,
81    unused_parens,
82    while_true,
83    missing_debug_implementations,
84    missing_docs,
85    trivial_casts,
86    trivial_numeric_casts,
87    unused_extern_crates,
88    unused_import_braces,
89    unused_qualifications,
90    unused_results
91)]
92#![forbid(unsafe_code)]
93
94use core::marker::PhantomData;
95pub use uuid;
96use uuid::Uuid;
97
98/// Errors which might occur when using [`Id`].
99#[derive(Debug, Clone, Copy)]
100pub enum Error {
101    /// Attempted to create an [`Id<T, Version>`] where the generic [`Uuid`] being converted from
102    /// was of a different Uuid version, than the one specified in the [`Id`] type.
103    WrongVersion {
104        /// Expected version, this is equivalent to the `Version` field of the [`Id`] type
105        expected: usize,
106        /// Actual version of the provided [`Uuid`]
107        actual: usize,
108    },
109}
110
111/// Typed wrapper around a [`Uuid`], supports same versions of Uuid as the `uuid` crate trough the `Version` parameter.
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113#[cfg_attr(feature = "serde", serde(transparent))]
114pub struct Id<T, Version>(
115    Uuid,
116    #[cfg_attr(feature = "serde", serde(skip))] PhantomData<(T, Version)>,
117);
118
119impl<T: Eq, Version: Eq> Eq for Id<T, Version> {}
120
121impl<T: Ord, Version: Ord> Ord for Id<T, Version> {
122    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
123        self.0.cmp(&other.0)
124    }
125}
126
127impl<T: PartialOrd, Version: PartialOrd> PartialOrd for Id<T, Version> {
128    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
129        self.0.partial_cmp(&other.0)
130    }
131}
132
133impl<T, Version> Copy for Id<T, Version> {}
134
135impl<T, Version> Clone for Id<T, Version> {
136    fn clone(&self) -> Self {
137        Self(self.0, self.1)
138    }
139}
140
141impl<T, Version> core::fmt::Debug for Id<T, Version> {
142    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
143        f.debug_tuple("Id").field(&self.0).finish()
144    }
145}
146
147impl<T, Version> core::fmt::Display for Id<T, Version> {
148    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
149        write!(f, "{}", self.0)
150    }
151}
152
153impl<T, Version> core::hash::Hash for Id<T, Version> {
154    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
155        self.0.hash(state);
156    }
157}
158
159impl<T, Version> AsRef<Uuid> for Id<T, Version> {
160    fn as_ref(&self) -> &Uuid {
161        &self.0
162    }
163}
164
165impl<T, Version> PartialEq<Id<T, Version>> for Id<T, Version> {
166    fn eq(&self, other: &Id<T, Version>) -> bool {
167        self.0 == other.0
168    }
169}
170
171impl<T, Version> PartialEq<Uuid> for Id<T, Version> {
172    fn eq(&self, other: &Uuid) -> bool {
173        &self.0 == other
174    }
175}
176
177#[cfg(feature = "v1")]
178pub use v1::V1;
179
180#[cfg(feature = "v3")]
181pub use v3::V3;
182
183#[cfg(feature = "v4")]
184pub use v4::V4;
185
186#[cfg(feature = "v5")]
187pub use v5::V5;
188
189#[cfg(all(unstable_uuid, feature = "v6"))]
190pub use v6::V6;
191
192#[cfg(all(unstable_uuid, feature = "v7"))]
193pub use v7::V7;
194
195#[cfg(all(unstable_uuid, feature = "v8"))]
196pub use v8::V8;
197
198#[cfg(feature = "v1")]
199mod v1 {
200    use crate::{Error, Id};
201    use core::marker::PhantomData;
202    use uuid::{Timestamp, Uuid};
203
204    /// Denotes that the contained Uuid is of type V1
205    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
206    pub struct V1;
207
208    impl<T> Id<T, V1> {
209        /// Construct a new typed v1 Uuid
210        #[allow(clippy::new_without_default)]
211        pub fn new(ts: Timestamp, node_id: &[u8; 6]) -> Self {
212            Self(Uuid::new_v1(ts, node_id), PhantomData)
213        }
214
215        /// Attempt to coerce a generic [`Uuid`] into a typed [`Id`]
216        ///
217        /// Returns `Err(Error::WrongVersion)` if the generic Uuid version
218        /// is not v1
219        pub fn from_generic_uuid(uuid: Uuid) -> Result<Self, Error> {
220            if uuid.get_version_num() == 1 {
221                Ok(Id(uuid, PhantomData))
222            } else {
223                Err(Error::WrongVersion {
224                    expected: 1,
225                    actual: uuid.get_version_num(),
226                })
227            }
228        }
229    }
230
231    #[cfg(test)]
232    mod tests {
233        use super::V1;
234        use crate::Id;
235        use uuid::Timestamp;
236
237        #[test]
238        fn new() {
239            let context = uuid::timestamp::context::Context::new_random();
240            let _ = Id::<u32, V1>::new(Timestamp::now(&context), &[0u8; 6]);
241        }
242    }
243}
244
245#[cfg(feature = "v3")]
246mod v3 {
247    use crate::{Error, Id, Uuid};
248    use core::marker::PhantomData;
249
250    /// Denotes that the contained Uuid is of type V3
251    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
252    pub struct V3;
253
254    impl<T> Id<T, V3> {
255        /// Construct a new typed v3 Uuid
256        #[allow(clippy::new_without_default)]
257        pub fn new(namespace: &Uuid, name: &[u8]) -> Self {
258            Self(Uuid::new_v3(namespace, name), PhantomData)
259        }
260
261        /// Attempt to coerce a generic [`Uuid`] into a typed [`Id`]
262        ///
263        /// Returns `Err(Error::WrongVersion)` if the generic Uuid version
264        /// is not v3
265        pub fn from_generic_uuid(uuid: Uuid) -> Result<Self, Error> {
266            if uuid.get_version_num() == 3 {
267                Ok(Id(uuid, PhantomData))
268            } else {
269                Err(Error::WrongVersion {
270                    expected: 3,
271                    actual: uuid.get_version_num(),
272                })
273            }
274        }
275    }
276
277    #[cfg(test)]
278    mod tests {
279        use super::V3;
280        use crate::Id;
281        use uuid::Uuid;
282
283        #[test]
284        fn new() {
285            let _ = Id::<u32, V3>::new(&Uuid::NAMESPACE_DNS, &[0u8; 6]);
286        }
287    }
288}
289
290#[cfg(feature = "v4")]
291mod v4 {
292    use crate::{Error, Id, Uuid};
293    use core::marker::PhantomData;
294
295    /// Denotes that the contained Uuid is of type V4
296    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
297    pub struct V4;
298
299    impl<T> Id<T, V4> {
300        /// Construct a new typed v4 Uuid
301        #[allow(clippy::new_without_default)]
302        pub fn new() -> Self {
303            Self(Uuid::new_v4(), PhantomData)
304        }
305
306        /// Attempt to coerce a generic [`Uuid`] into a typed [`Id`]
307        ///
308        /// Returns `Err(Error::WrongVersion)` if the generic Uuid version
309        /// is not v4
310        pub fn from_generic_uuid(uuid: Uuid) -> Result<Self, Error> {
311            if uuid.get_version_num() == 4 {
312                Ok(Id(uuid, PhantomData))
313            } else {
314                Err(Error::WrongVersion {
315                    expected: 4,
316                    actual: uuid.get_version_num(),
317                })
318            }
319        }
320    }
321
322    #[cfg(test)]
323    mod tests {
324        use super::V4;
325        use crate::Id;
326
327        #[test]
328        fn new() {
329            let _ = Id::<u32, V4>::new();
330        }
331    }
332}
333
334#[cfg(feature = "v5")]
335mod v5 {
336    use crate::{Error, Id, Uuid};
337    use core::marker::PhantomData;
338
339    /// Denotes that the contained Uuid is of type V5
340    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
341    pub struct V5;
342
343    impl<T> Id<T, V5> {
344        /// Construct a new typed v5 Uuid
345        #[allow(clippy::new_without_default)]
346        pub fn new(namespace: &Uuid, name: &[u8]) -> Self {
347            Self(Uuid::new_v5(namespace, name), PhantomData)
348        }
349
350        /// Attempt to coerce a generic [`Uuid`] into a typed [`Id`]
351        ///
352        /// Returns `Err(Error::WrongVersion)` if the generic Uuid version
353        /// is not v5
354        pub fn from_generic_uuid(uuid: Uuid) -> Result<Self, Error> {
355            if uuid.get_version_num() == 5 {
356                Ok(Id(uuid, PhantomData))
357            } else {
358                Err(Error::WrongVersion {
359                    expected: 5,
360                    actual: uuid.get_version_num(),
361                })
362            }
363        }
364    }
365
366    #[cfg(test)]
367    mod tests {
368        use super::V5;
369        use crate::Id;
370        use uuid::Uuid;
371
372        #[test]
373        fn new() {
374            let _ = Id::<u32, V5>::new(&Uuid::NAMESPACE_DNS, &[0u8; 6]);
375        }
376    }
377}
378
379#[cfg(all(uuid_unstable, feature = "v6"))]
380mod v6 {
381    use crate::{Error, Id};
382    use core::marker::PhantomData;
383    use uuid::{Timestamp, Uuid};
384
385    /// Denotes that the contained Uuid is of type V6
386    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
387    pub struct V6;
388
389    impl<T> Id<T, V6> {
390        /// Construct a new typed v6 Uuid
391        #[allow(clippy::new_without_default)]
392        pub fn new(ts: Timestamp, node_id: &[u8; 6]) -> Self {
393            Self(Uuid::new_v6(ts, node_id), PhantomData)
394        }
395
396        /// Attempt to coerce a generic [`Uuid`] into a typed [`Id`]
397        ///
398        /// Returns `Err(Error::WrongVersion)` if the generic Uuid version
399        /// is not v6
400        pub fn from_generic_uuid(uuid: Uuid) -> Result<Self, Error> {
401            if uuid.get_version_num() == 6 {
402                Ok(Id(uuid, PhantomData))
403            } else {
404                Err(Error::WrongVersion {
405                    expected: 6,
406                    actual: uuid.get_version_num(),
407                })
408            }
409        }
410    }
411}
412
413#[cfg(all(uuid_unstable, feature = "v7"))]
414mod v7 {
415    use crate::{Error, Id};
416    use core::marker::PhantomData;
417    use uuid::{Timestamp, Uuid};
418
419    /// Denotes that the contained Uuid is of type V7
420    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
421    pub struct V7;
422
423    impl<T> Id<T, V7> {
424        /// Construct a new typed v7 Uuid
425        #[allow(clippy::new_without_default)]
426        pub fn new(ts: Timestamp) -> Self {
427            Self(Uuid::new_v7(ts), PhantomData)
428        }
429
430        /// Attempt to coerce a generic [`Uuid`] into a typed [`Id`]
431        ///
432        /// Returns `Err(Error::WrongVersion)` if the generic Uuid version
433        /// is not v7
434        pub fn from_generic_uuid(uuid: Uuid) -> Result<Self, Error> {
435            if uuid.get_version_num() == 7 {
436                Ok(Id(uuid, PhantomData))
437            } else {
438                Err(Error::WrongVersion {
439                    expected: 7,
440                    actual: uuid.get_version_num(),
441                })
442            }
443        }
444    }
445}
446
447#[cfg(all(uuid_unstable, feature = "v8"))]
448mod v8 {
449    use crate::{Error, Id, Uuid};
450    use core::marker::PhantomData;
451
452    /// Denotes that the contained Uuid is of type V8
453    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
454    pub struct V8;
455
456    impl<T> Id<T, V8> {
457        /// Construct a new typed v8 Uuid
458        #[allow(clippy::new_without_default)]
459        pub fn new(buf: [u8; 16]) -> Self {
460            Self(Uuid::new_v8(buf), PhantomData)
461        }
462
463        /// Attempt to coerce a generic [`Uuid`] into a typed [`Id`]
464        ///
465        /// Returns `Err(Error::WrongVersion)` if the generic Uuid version
466        /// is not v8
467        pub fn from_generic_uuid(uuid: Uuid) -> Result<Self, Error> {
468            if uuid.get_version_num() == 8 {
469                Ok(Id(uuid, PhantomData))
470            } else {
471                Err(Error::WrongVersion {
472                    expected: 8,
473                    actual: uuid.get_version_num(),
474                })
475            }
476        }
477    }
478}