Skip to main content

newtype_uuid/
lib.rs

1//! A newtype wrapper around [`Uuid`].
2//!
3//! # Motivation
4//!
5//! Many large systems use UUIDs as unique identifiers for various entities. However, the [`Uuid`]
6//! type does not carry information about the kind of entity it identifies, which can lead to mixing
7//! up different types of UUIDs at runtime.
8//!
9//! This crate provides a wrapper type around [`Uuid`] that allows you to specify the kind of entity
10//! the UUID identifies.
11//!
12//! # Example
13//!
14//! ```
15//! use newtype_uuid::{GenericUuid, TypedUuid, TypedUuidKind, TypedUuidTag};
16//!
17//! // First, define a type that represents the kind of UUID this is.
18//! enum MyKind {}
19//!
20//! impl TypedUuidKind for MyKind {
21//!     fn tag() -> TypedUuidTag {
22//!         // Tags are required to be ASCII identifiers, with underscores
23//!         // and dashes also supported. The validity of a tag can be checked
24//!         // at compile time by assigning it to a const, like so:
25//!         const TAG: TypedUuidTag = TypedUuidTag::new("my_kind");
26//!         TAG
27//!     }
28//! }
29//!
30//! // Now, a UUID can be created with this kind.
31//! let uuid: TypedUuid<MyKind> = "dffc3068-1cd6-47d5-b2f3-636b41b07084".parse().unwrap();
32//!
33//! // The Display (and therefore ToString) impls still show the same value.
34//! assert_eq!(uuid.to_string(), "dffc3068-1cd6-47d5-b2f3-636b41b07084");
35//!
36//! // The Debug impl will show the tag as well.
37//! assert_eq!(
38//!     format!("{:?}", uuid),
39//!     "dffc3068-1cd6-47d5-b2f3-636b41b07084 (my_kind)"
40//! );
41//! ```
42//!
43//! If you have a large number of UUID kinds, consider using
44//! [`newtype-uuid-macros`] which comes with several convenience features.
45//!
46//! ```
47//! use newtype_uuid_macros::impl_typed_uuid_kinds;
48//!
49//! // Invoke this macro with:
50//! impl_typed_uuid_kinds! {
51//!     kinds = {
52//!         User = {},
53//!         Project = {},
54//!         // ...
55//!     },
56//! }
57//! ```
58//!
59//! See [`newtype-uuid-macros`] for more information.
60//!
61//! [`newtype-uuid-macros`]: https://docs.rs/newtype-uuid-macros
62//!
63//! For simpler cases, you can also write your own declarative macro. Use this
64//! template to get started:
65//!
66//! ```rust
67//! # use newtype_uuid::{TypedUuidKind, TypedUuidTag};
68//! macro_rules! impl_kinds {
69//!     ($($kind:ident => $tag:literal),* $(,)?) => {
70//!         $(
71//!             pub enum $kind {}
72//!
73//!             impl TypedUuidKind for $kind {
74//!                 #[inline]
75//!                 fn tag() -> TypedUuidTag {
76//!                     const TAG: TypedUuidTag = TypedUuidTag::new($tag);
77//!                     TAG
78//!                 }
79//!             }
80//!         )*
81//!     };
82//! }
83//!
84//! // Invoke this macro with:
85//! impl_kinds! {
86//!     UserKind => "user",
87//!     ProjectKind => "project",
88//! }
89//! ```
90//!
91//! # Implementations
92//!
93//! In general, [`TypedUuid`] uses the same wire and serialization formats as [`Uuid`]. This means
94//! that persistent representations of [`TypedUuid`] are the same as [`Uuid`]; [`TypedUuid`] is
95//! intended to be helpful within Rust code, not across serialization boundaries.
96//!
97//! - The `Display` and `FromStr` impls are forwarded to the underlying [`Uuid`].
98//! - If the `serde` feature is enabled, `TypedUuid` will serialize and deserialize using the same
99//!   format as [`Uuid`].
100//! - If the `schemars08` feature is enabled, [`TypedUuid`] will implement `JsonSchema` if the
101//!   corresponding [`TypedUuidKind`] implements `JsonSchema`.
102//!
103//! To abstract over typed and untyped UUIDs, the [`GenericUuid`] trait is provided. This trait also
104//! permits conversions between typed and untyped UUIDs.
105//!
106//! # Dependencies
107//!
108//! - The only required dependency is the [`uuid`] crate. Optional features may add further
109//!   dependencies.
110//!
111//! # Features
112//!
113//! - `default`: Enables default features in the newtype-uuid crate.
114//! - `std`: Enables the use of the standard library. *Enabled by default.*
115//! - `serde`: Enables serialization and deserialization support via Serde. *Not enabled by
116//!   default.*
117//! - `v4`: Enables the `new_v4` method for generating UUIDs. *Not enabled by default.*
118//! - `schemars08`: Enables support for generating JSON schemas via schemars 0.8. *Not enabled by
119//!   default.* Note that the format of the generated schema is **not currently part** of the stable
120//!   API, though we hope to stabilize it in the future.
121//! - `proptest1`: Enables support for generating `proptest::Arbitrary` instances of UUIDs. *Not enabled by default.*
122//!
123//! # Minimum supported Rust version (MSRV)
124//!
125//! The MSRV of this crate is **Rust 1.85.** In general, this crate will follow the MSRV of the
126//! underlying `uuid` crate or of dependencies, with an aim to be conservative.
127//!
128//! Within the 1.x series, MSRV updates will be accompanied by a minor version bump. The MSRVs for
129//! each minor version are:
130//!
131//! * Version **1.0.x**: Rust 1.60.
132//! * Version **1.1.x**: Rust 1.61. This permits `TypedUuid<T>` to have `const fn` methods.
133//! * Version **1.2.x**: Rust 1.67, required by some dependency updates.
134//! * Version **1.3.x**: Rust 1.79, required by some dependency updates.
135//! * Version **1.4.x**: Rust 1.85, required by some dependency updates.
136//!
137//! # Alternatives
138//!
139//! - [`typed-uuid`](https://crates.io/crates/typed-uuid): generally similar, but with a few design
140//!   decisions that are different.
141
142#![forbid(unsafe_code)]
143#![warn(missing_docs)]
144#![cfg_attr(not(feature = "std"), no_std)]
145#![cfg_attr(doc_cfg, feature(doc_cfg))]
146
147#[cfg(feature = "alloc")]
148extern crate alloc;
149
150/// Macro support for [`newtype-uuid-macros`].
151///
152/// This module re-exports types needed for [`newtype-uuid-macros`] to work.
153///
154/// [`newtype-uuid-macros`]: https://docs.rs/newtype-uuid-macros
155#[doc(hidden)]
156pub mod macro_support {
157    #[cfg(feature = "schemars08")]
158    pub use schemars as schemars08;
159    #[cfg(feature = "schemars08")]
160    pub use serde_json;
161}
162
163use core::{
164    cmp::Ordering,
165    fmt,
166    hash::{Hash, Hasher},
167    marker::PhantomData,
168    str::FromStr,
169};
170#[cfg(feature = "v7")]
171pub use uuid::Timestamp;
172use uuid::{Uuid, Version};
173
174/// A UUID with type-level information about what it's used for.
175///
176/// For more, see [the library documentation](crate).
177#[repr(transparent)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
179#[cfg_attr(feature = "serde", serde(transparent, bound = ""))]
180pub struct TypedUuid<T: TypedUuidKind> {
181    uuid: Uuid,
182    _phantom: PhantomData<T>,
183}
184
185impl<T: TypedUuidKind> TypedUuid<T> {
186    /// The 'nil UUID' (all zeros).
187    ///
188    /// The nil UUID is a special form of UUID that is specified to have all
189    /// 128 bits set to zero.
190    ///
191    /// # References
192    ///
193    /// * [Nil UUID in RFC4122](https://tools.ietf.org/html/rfc4122.html#section-4.1.7)
194    #[inline]
195    #[must_use]
196    pub const fn nil() -> Self {
197        Self {
198            uuid: Uuid::nil(),
199            _phantom: PhantomData,
200        }
201    }
202
203    /// The 'max UUID' (all ones).
204    ///
205    /// The max UUID is a special form of UUID that is specified to have all
206    /// 128 bits set to one.
207    ///
208    /// # References
209    ///
210    /// * [Max UUID in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.4)
211    #[inline]
212    #[must_use]
213    pub const fn max() -> Self {
214        Self {
215            uuid: Uuid::max(),
216            _phantom: PhantomData,
217        }
218    }
219
220    /// Creates a UUID from four field values.
221    #[inline]
222    #[must_use]
223    pub const fn from_fields(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
224        Self {
225            uuid: Uuid::from_fields(d1, d2, d3, &d4),
226            _phantom: PhantomData,
227        }
228    }
229
230    /// Creates a UUID from four field values in little-endian order.
231    ///
232    /// The bytes in the `d1`, `d2` and `d3` fields will be flipped to convert into big-endian
233    /// order. This is based on the endianness of the UUID, rather than the target environment so
234    /// bytes will be flipped on both big and little endian machines.
235    #[inline]
236    #[must_use]
237    pub const fn from_fields_le(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
238        Self {
239            uuid: Uuid::from_fields_le(d1, d2, d3, &d4),
240            _phantom: PhantomData,
241        }
242    }
243
244    /// Creates a UUID from a 128bit value.
245    #[inline]
246    #[must_use]
247    pub const fn from_u128(value: u128) -> Self {
248        Self {
249            uuid: Uuid::from_u128(value),
250            _phantom: PhantomData,
251        }
252    }
253
254    /// Creates a UUID from a 128bit value in little-endian order.
255    ///
256    /// The entire value will be flipped to convert into big-endian order. This is based on the
257    /// endianness of the UUID, rather than the target environment so bytes will be flipped on both
258    /// big and little endian machines.
259    #[inline]
260    #[must_use]
261    pub const fn from_u128_le(value: u128) -> Self {
262        Self {
263            uuid: Uuid::from_u128_le(value),
264            _phantom: PhantomData,
265        }
266    }
267
268    /// Creates a UUID from two 64bit values.
269    #[inline]
270    #[must_use]
271    pub const fn from_u64_pair(d1: u64, d2: u64) -> Self {
272        Self {
273            uuid: Uuid::from_u64_pair(d1, d2),
274            _phantom: PhantomData,
275        }
276    }
277
278    /// Creates a UUID using the supplied bytes.
279    #[inline]
280    #[must_use]
281    pub const fn from_bytes(bytes: uuid::Bytes) -> Self {
282        Self {
283            uuid: Uuid::from_bytes(bytes),
284            _phantom: PhantomData,
285        }
286    }
287
288    /// Creates a UUID using the supplied bytes in little-endian order.
289    ///
290    /// The individual fields encoded in the buffer will be flipped.
291    #[inline]
292    #[must_use]
293    pub const fn from_bytes_le(bytes: uuid::Bytes) -> Self {
294        Self {
295            uuid: Uuid::from_bytes_le(bytes),
296            _phantom: PhantomData,
297        }
298    }
299
300    /// Creates a new, random UUID v4 of this type.
301    #[inline]
302    #[cfg(feature = "v4")]
303    #[must_use]
304    pub fn new_v4() -> Self {
305        Self::from_untyped_uuid(Uuid::new_v4())
306    }
307
308    /// Creates a new, random UUID v7 of this type.
309    #[inline]
310    #[cfg(feature = "v7")]
311    #[must_use]
312    pub fn new_v7(ts: uuid::Timestamp) -> Self {
313        Self::from_untyped_uuid(Uuid::new_v7(ts))
314    }
315
316    /// Returns the version number of the UUID.
317    ///
318    /// This represents the algorithm used to generate the value.
319    /// This method is the future-proof alternative to [`Self::get_version`].
320    ///
321    /// # References
322    ///
323    /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
324    #[inline]
325    pub const fn get_version_num(&self) -> usize {
326        self.uuid.get_version_num()
327    }
328
329    /// Returns the version of the UUID.
330    ///
331    /// This represents the algorithm used to generate the value.
332    /// If the version field doesn't contain a recognized version then `None`
333    /// is returned. If you're trying to read the version for a future extension
334    /// you can also use [`Uuid::get_version_num`] to unconditionally return a
335    /// number. Future extensions may start to return `Some` once they're
336    /// standardized and supported.
337    ///
338    /// # References
339    ///
340    /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
341    #[inline]
342    pub fn get_version(&self) -> Option<Version> {
343        self.uuid.get_version()
344    }
345
346    /// Returns true if the UUID is nil (all zeros).
347    #[inline]
348    pub const fn is_nil(&self) -> bool {
349        self.uuid.is_nil()
350    }
351
352    /// Returns true if the UUID is the max value (all ones).
353    #[inline]
354    pub const fn is_max(&self) -> bool {
355        self.uuid.is_max()
356    }
357
358    /// Returns the four field values of the UUID.
359    ///
360    /// These values can be passed to [`Self::from_fields`] to reconstruct the
361    /// original UUID. The first field represents the initial eight hex digits
362    /// as a big-endian `u32`. The second and third fields represent subsequent
363    /// hex digit groups as `u16` values. The final field contains the last two
364    /// groups of hex digits as an 8-byte array.
365    ///
366    /// # Examples
367    ///
368    /// ```
369    /// # use newtype_uuid::TypedUuid;
370    /// # enum ExampleKind {}
371    /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
372    /// #     fn tag() -> newtype_uuid::TypedUuidTag {
373    /// #         const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
374    /// #         TAG
375    /// #     }
376    /// # }
377    /// let uuid: TypedUuid<ExampleKind> =
378    ///     "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
379    ///
380    /// assert_eq!(
381    ///     uuid.as_fields(),
382    ///     (
383    ///         0xa1a2a3a4,
384    ///         0xb1b2,
385    ///         0xc1c2,
386    ///         &[0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8],
387    ///     )
388    /// );
389    /// ```
390    #[inline]
391    pub fn as_fields(&self) -> (u32, u16, u16, &[u8; 8]) {
392        self.uuid.as_fields()
393    }
394
395    /// Returns the four field values in little-endian order.
396    ///
397    /// The bytes within integer fields are converted from big-endian order.
398    /// This is based on the endianness of the UUID rather than the target
399    /// environment, so bytes will be flipped on both big and little endian
400    /// machines.
401    ///
402    /// # Examples
403    ///
404    /// ```
405    /// # use newtype_uuid::TypedUuid;
406    /// # enum ExampleKind {}
407    /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
408    /// #     fn tag() -> newtype_uuid::TypedUuidTag {
409    /// #         const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
410    /// #         TAG
411    /// #     }
412    /// # }
413    /// let uuid: TypedUuid<ExampleKind> =
414    ///     "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
415    ///
416    /// assert_eq!(
417    ///     uuid.to_fields_le(),
418    ///     (
419    ///         0xa4a3a2a1,
420    ///         0xb2b1,
421    ///         0xc2c1,
422    ///         &[0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8],
423    ///     )
424    /// );
425    /// ```
426    #[inline]
427    pub fn to_fields_le(&self) -> (u32, u16, u16, &[u8; 8]) {
428        self.uuid.to_fields_le()
429    }
430
431    /// Returns a 128-bit value containing the UUID bytes.
432    ///
433    /// # Examples
434    ///
435    /// ```
436    /// # use newtype_uuid::TypedUuid;
437    /// # enum ExampleKind {}
438    /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
439    /// #     fn tag() -> newtype_uuid::TypedUuidTag {
440    /// #         const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
441    /// #         TAG
442    /// #     }
443    /// # }
444    /// let uuid: TypedUuid<ExampleKind> =
445    ///     "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
446    ///
447    /// assert_eq!(
448    ///     uuid.as_u128(),
449    ///     0xa1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8u128,
450    /// );
451    /// ```
452    #[inline]
453    pub const fn as_u128(&self) -> u128 {
454        self.uuid.as_u128()
455    }
456
457    /// Returns a 128-bit little-endian value.
458    ///
459    /// The bytes in the `u128` will be flipped to convert into big-endian order.
460    /// This is based on the endianness of the UUID, rather than the target
461    /// environment so bytes will be flipped on both big and little endian
462    /// machines.
463    ///
464    /// Note that this will produce a different result than
465    /// [`Self::to_fields_le`], because the entire UUID is reversed, rather than
466    /// reversing the individual fields in-place.
467    ///
468    /// # Examples
469    ///
470    /// ```
471    /// # use newtype_uuid::TypedUuid;
472    /// # enum ExampleKind {}
473    /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
474    /// #     fn tag() -> newtype_uuid::TypedUuidTag {
475    /// #         const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
476    /// #         TAG
477    /// #     }
478    /// # }
479    /// let uuid: TypedUuid<ExampleKind> =
480    ///     "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
481    ///
482    /// assert_eq!(
483    ///     uuid.to_u128_le(),
484    ///     0xd8d7d6d5d4d3d2d1c2c1b2b1a4a3a2a1u128,
485    /// );
486    /// ```
487    #[inline]
488    pub fn to_u128_le(&self) -> u128 {
489        self.uuid.to_u128_le()
490    }
491
492    /// Returns two 64-bit values representing the UUID.
493    ///
494    /// The first `u64` contains the most significant 64 bits; the second
495    /// contains the least significant bits.
496    ///
497    /// # Examples
498    ///
499    /// ```
500    /// # use newtype_uuid::TypedUuid;
501    /// # enum ExampleKind {}
502    /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
503    /// #     fn tag() -> newtype_uuid::TypedUuidTag {
504    /// #         const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
505    /// #         TAG
506    /// #     }
507    /// # }
508    /// let uuid: TypedUuid<ExampleKind> =
509    ///     "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
510    ///
511    /// assert_eq!(
512    ///     uuid.as_u64_pair(),
513    ///     (0xa1a2a3a4b1b2c1c2, 0xd1d2d3d4d5d6d7d8),
514    /// );
515    /// ```
516    #[inline]
517    pub const fn as_u64_pair(&self) -> (u64, u64) {
518        self.uuid.as_u64_pair()
519    }
520
521    /// Returns a slice of 16 octets containing the value.
522    ///
523    /// This method borrows the underlying byte value of the UUID.
524    ///
525    /// # Examples
526    ///
527    /// ```
528    /// # use newtype_uuid::TypedUuid;
529    /// # enum ExampleKind {}
530    /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
531    /// #     fn tag() -> newtype_uuid::TypedUuidTag {
532    /// #         const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
533    /// #         TAG
534    /// #     }
535    /// # }
536    /// let bytes = [
537    ///     0xa1, 0xa2, 0xa3, 0xa4,
538    ///     0xb1, 0xb2,
539    ///     0xc1, 0xc2,
540    ///     0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
541    /// ];
542    ///
543    /// let uuid = TypedUuid::<ExampleKind>::from_bytes(bytes);
544    /// let bytes2 = uuid.as_bytes();
545    ///
546    /// assert_eq!(&bytes, bytes2);
547    /// ```
548    #[inline]
549    pub const fn as_bytes(&self) -> &uuid::Bytes {
550        self.uuid.as_bytes()
551    }
552
553    /// Consumes self and returns the underlying byte value of the UUID.
554    ///
555    /// # Examples
556    ///
557    /// ```
558    /// # use newtype_uuid::TypedUuid;
559    /// # enum ExampleKind {}
560    /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
561    /// #     fn tag() -> newtype_uuid::TypedUuidTag {
562    /// #         const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
563    /// #         TAG
564    /// #     }
565    /// # }
566    /// let bytes = [
567    ///     0xa1, 0xa2, 0xa3, 0xa4,
568    ///     0xb1, 0xb2,
569    ///     0xc1, 0xc2,
570    ///     0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
571    /// ];
572    ///
573    /// let uuid = TypedUuid::<ExampleKind>::from_bytes(bytes);
574    ///
575    /// assert_eq!(bytes, uuid.into_bytes());
576    /// ```
577    #[inline]
578    #[must_use]
579    pub const fn into_bytes(self) -> uuid::Bytes {
580        self.uuid.into_bytes()
581    }
582
583    /// Returns the bytes of the UUID in little-endian order.
584    ///
585    /// The bytes will be flipped to convert into little-endian order. This is
586    /// based on the endianness of the UUID, rather than the target environment
587    /// so bytes will be flipped on both big and little endian machines.
588    ///
589    /// # Examples
590    ///
591    /// ```
592    /// # use newtype_uuid::TypedUuid;
593    /// # enum ExampleKind {}
594    /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
595    /// #     fn tag() -> newtype_uuid::TypedUuidTag {
596    /// #         const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
597    /// #         TAG
598    /// #     }
599    /// # }
600    /// let uuid: TypedUuid<ExampleKind> =
601    ///     "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
602    ///
603    /// assert_eq!(
604    ///     uuid.to_bytes_le(),
605    ///     [
606    ///         0xa4, 0xa3, 0xa2, 0xa1,
607    ///         0xb2, 0xb1,
608    ///         0xc2, 0xc1,
609    ///         0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
610    ///     ]
611    /// );
612    /// ```
613    #[inline]
614    pub fn to_bytes_le(&self) -> uuid::Bytes {
615        self.uuid.to_bytes_le()
616    }
617
618    /// Converts the UUID to one with looser semantics.
619    ///
620    /// By default, UUID kinds are considered independent, and conversions
621    /// between them must happen via the [`GenericUuid`] interface. But in some
622    /// cases, there may be a relationship between two different UUID kinds, and
623    /// you may wish to easily convert UUIDs from one kind to another.
624    ///
625    /// Typically, a conversion from `TypedUuid<T>` to `TypedUuid<U>` is most
626    /// useful when `T`'s semantics are a superset of `U`'s, or in other words,
627    /// when every `TypedUuid<T>` is logically also a `TypedUuid<U>`.
628    ///
629    /// For instance:
630    ///
631    /// * Imagine you have [`TypedUuidKind`]s for different types of
632    ///   database connections, where `DbConnKind` is the general type
633    ///   and `PgConnKind` is a specific kind for Postgres.
634    /// * Since every Postgres connection is also a database connection,
635    ///   a cast from `TypedUuid<PgConnKind>` to `TypedUuid<DbConnKind>`
636    ///   makes sense.
637    /// * The inverse cast would not make sense, as a database connection may not
638    ///   necessarily be a Postgres connection.
639    ///
640    /// This interface provides an alternative, safer way to perform this
641    /// conversion. Indicate your intention to allow a conversion between kinds
642    /// by implementing `From<T> for U`, as shown in the example below.
643    ///
644    /// # Examples
645    ///
646    /// ```
647    /// use newtype_uuid::{TypedUuid, TypedUuidKind, TypedUuidTag};
648    ///
649    /// // Let's say that these UUIDs represent repositories for different
650    /// // version control systems, such that you have a generic RepoKind:
651    /// pub enum RepoKind {}
652    /// impl TypedUuidKind for RepoKind {
653    ///     fn tag() -> TypedUuidTag {
654    ///         const TAG: TypedUuidTag = TypedUuidTag::new("repo");
655    ///         TAG
656    ///     }
657    /// }
658    ///
659    /// // You also have more specific kinds:
660    /// pub enum GitRepoKind {}
661    /// impl TypedUuidKind for GitRepoKind {
662    ///     fn tag() -> TypedUuidTag {
663    ///         const TAG: TypedUuidTag = TypedUuidTag::new("git_repo");
664    ///         TAG
665    ///     }
666    /// }
667    /// // (and HgRepoKind, JujutsuRepoKind, etc...)
668    ///
669    /// // First, define a `From` impl. This impl indicates your desire
670    /// // to convert from one kind to another.
671    /// impl From<GitRepoKind> for RepoKind {
672    ///     fn from(value: GitRepoKind) -> Self {
673    ///         match value {}
674    ///     }
675    /// }
676    ///
677    /// // Now you can convert between them:
678    /// let git_uuid: TypedUuid<GitRepoKind> =
679    ///     TypedUuid::from_u128(0xe9245204_34ea_4ca7_a1c6_2e94fa49df61);
680    /// let repo_uuid: TypedUuid<RepoKind> = git_uuid.upcast();
681    /// ```
682    #[inline]
683    #[must_use]
684    pub const fn upcast<U: TypedUuidKind>(self) -> TypedUuid<U>
685    where
686        T: Into<U>,
687    {
688        TypedUuid {
689            uuid: self.uuid,
690            _phantom: PhantomData,
691        }
692    }
693}
694
695// ---
696// Trait impls
697// ---
698
699impl<T: TypedUuidKind> PartialEq for TypedUuid<T> {
700    #[inline]
701    fn eq(&self, other: &Self) -> bool {
702        self.uuid.eq(&other.uuid)
703    }
704}
705
706impl<T: TypedUuidKind> Eq for TypedUuid<T> {}
707
708impl<T: TypedUuidKind> PartialOrd for TypedUuid<T> {
709    #[inline]
710    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
711        Some(self.cmp(other))
712    }
713}
714
715impl<T: TypedUuidKind> Ord for TypedUuid<T> {
716    #[inline]
717    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
718        self.uuid.cmp(&other.uuid)
719    }
720}
721
722impl<T: TypedUuidKind> Hash for TypedUuid<T> {
723    #[inline]
724    fn hash<H: Hasher>(&self, state: &mut H) {
725        self.uuid.hash(state);
726    }
727}
728
729impl<T: TypedUuidKind> fmt::Debug for TypedUuid<T> {
730    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
731        self.uuid.fmt(f)?;
732        write!(f, " ({})", T::tag())
733    }
734}
735
736impl<T: TypedUuidKind> fmt::Display for TypedUuid<T> {
737    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738        self.uuid.fmt(f)
739    }
740}
741
742impl<T: TypedUuidKind> Clone for TypedUuid<T> {
743    #[inline]
744    fn clone(&self) -> Self {
745        *self
746    }
747}
748
749impl<T: TypedUuidKind> Copy for TypedUuid<T> {}
750
751impl<T: TypedUuidKind> FromStr for TypedUuid<T> {
752    type Err = ParseError;
753
754    fn from_str(s: &str) -> Result<Self, Self::Err> {
755        let uuid = Uuid::from_str(s).map_err(|error| ParseError {
756            error,
757            tag: T::tag(),
758        })?;
759        Ok(Self::from_untyped_uuid(uuid))
760    }
761}
762
763impl<T: TypedUuidKind> Default for TypedUuid<T> {
764    #[inline]
765    fn default() -> Self {
766        Self::from_untyped_uuid(Uuid::default())
767    }
768}
769
770impl<T: TypedUuidKind> AsRef<[u8]> for TypedUuid<T> {
771    #[inline]
772    fn as_ref(&self) -> &[u8] {
773        self.uuid.as_ref()
774    }
775}
776
777#[cfg(feature = "alloc")]
778impl<T: TypedUuidKind> From<TypedUuid<T>> for alloc::vec::Vec<u8> {
779    #[inline]
780    fn from(typed_uuid: TypedUuid<T>) -> Self {
781        typed_uuid.into_untyped_uuid().into_bytes().to_vec()
782    }
783}
784
785#[cfg(feature = "schemars08")]
786mod schemars08_imp {
787    use super::*;
788    use schemars::{
789        JsonSchema, SchemaGenerator,
790        schema::{InstanceType, Schema, SchemaObject},
791        schema_for,
792    };
793
794    const CRATE_NAME: &str = "newtype-uuid";
795    const CRATE_VERSION: &str = "1";
796    const CRATE_PATH: &str = "newtype_uuid::TypedUuid";
797
798    /// Implements `JsonSchema` for `TypedUuid<T>`, if `T` implements `JsonSchema`.
799    ///
800    /// * `schema_name` is set to `"TypedUuidFor"`, concatenated by the schema name of `T`.
801    /// * `schema_id` is set to `format!("newtype_uuid::TypedUuid<{}>", T::schema_id())`.
802    /// * `json_schema` is the same as the one for `Uuid`, with the `x-rust-type` extension
803    ///   to allow automatic replacement in typify and progenitor.
804    impl<T> JsonSchema for TypedUuid<T>
805    where
806        T: TypedUuidKind + JsonSchema,
807    {
808        #[inline]
809        fn schema_name() -> String {
810            // Use the alias if available, otherwise generate our own schema name.
811            if let Some(alias) = T::alias() {
812                alias.to_owned()
813            } else {
814                format!("TypedUuidFor{}", T::schema_name())
815            }
816        }
817
818        #[inline]
819        fn schema_id() -> std::borrow::Cow<'static, str> {
820            std::borrow::Cow::Owned(format!("newtype_uuid::TypedUuid<{}>", T::schema_id()))
821        }
822
823        #[inline]
824        fn json_schema(generator: &mut SchemaGenerator) -> Schema {
825            // Look at the schema for `T`. If it has `x-rust-type`, *and* if an
826            // alias is available, we can lift up the `x-rust-type` into our own schema.
827            //
828            // We use a new schema generator for `T` to avoid T's schema being
829            // added to the list of schemas in `generator` in case the lifting
830            // is successful.
831            let t_schema = schema_for!(T);
832            if let Some(schema) = lift_json_schema(&t_schema.schema, T::alias()) {
833                return schema.into();
834            }
835
836            SchemaObject {
837                instance_type: Some(InstanceType::String.into()),
838                format: Some("uuid".to_string()),
839                extensions: [(
840                    "x-rust-type".to_string(),
841                    serde_json::json!({
842                        "crate": CRATE_NAME,
843                        "version": CRATE_VERSION,
844                        "path": CRATE_PATH,
845                        "parameters": [generator.subschema_for::<T>()]
846                    }),
847                )]
848                .into_iter()
849                .collect(),
850                ..Default::default()
851            }
852            .into()
853        }
854    }
855
856    // ? on Option is too easy to make mistakes with, so we use `let Some(..) =
857    // .. else` instead.
858    #[allow(clippy::question_mark)]
859    fn lift_json_schema(schema: &SchemaObject, alias: Option<&str>) -> Option<SchemaObject> {
860        let Some(alias) = alias else {
861            return None;
862        };
863
864        let Some(v) = schema.extensions.get("x-rust-type") else {
865            return None;
866        };
867
868        // The crate, version and path must all be present.
869        let Some(crate_) = v.get("crate") else {
870            return None;
871        };
872        let Some(version) = v.get("version") else {
873            return None;
874        };
875        let Some(path) = v.get("path").and_then(|p| p.as_str()) else {
876            return None;
877        };
878        let Some((module_path, _)) = path.rsplit_once("::") else {
879            return None;
880        };
881
882        // The preconditions are all met. We can lift the schema by appending
883        // the alias to the module path.
884        let alias_path = format!("{module_path}::{alias}");
885
886        Some(SchemaObject {
887            instance_type: Some(InstanceType::String.into()),
888            format: Some("uuid".to_string()),
889            extensions: [(
890                "x-rust-type".to_string(),
891                serde_json::json!({
892                    "crate": crate_,
893                    "version": version,
894                    "path": alias_path,
895                }),
896            )]
897            .into_iter()
898            .collect(),
899            ..Default::default()
900        })
901    }
902}
903
904#[cfg(feature = "proptest1")]
905mod proptest1_imp {
906    use super::*;
907    use proptest::{
908        arbitrary::{Arbitrary, any},
909        strategy::{BoxedStrategy, Strategy},
910    };
911
912    /// Parameters for use with `proptest` instances.
913    ///
914    /// This is currently not exported as a type because it has no options. But
915    /// it's left in as an extension point for the future.
916    #[derive(Clone, Debug, Default)]
917    pub struct TypedUuidParams(());
918
919    /// Generates random `TypedUuid<T>` instances.
920    ///
921    /// Currently, this always returns a version 4 UUID. Support for other kinds
922    /// of UUIDs might be added via [`Self::Parameters`] in the future.
923    impl<T> Arbitrary for TypedUuid<T>
924    where
925        T: TypedUuidKind,
926    {
927        type Parameters = TypedUuidParams;
928        type Strategy = BoxedStrategy<Self>;
929
930        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
931            let bytes = any::<[u8; 16]>();
932            bytes
933                .prop_map(|b| {
934                    let uuid = uuid::Builder::from_random_bytes(b).into_uuid();
935                    TypedUuid::<T>::from_untyped_uuid(uuid)
936                })
937                .boxed()
938        }
939    }
940}
941
942/// Represents marker types that can be used as a type parameter for [`TypedUuid`].
943///
944/// Generally, an implementation of this will be a zero-sized type that can never be constructed. An
945/// empty struct or enum works well for this.
946///
947/// # Implementations
948///
949/// If the `schemars08` feature is enabled, and [`JsonSchema`] is implemented for a kind `T`, then
950/// [`TypedUuid`]`<T>` will also implement [`JsonSchema`].
951///
952/// If you have a large number of UUID kinds, consider using
953/// [`newtype-uuid-macros`] which comes with several convenience features.
954///
955/// ```
956/// use newtype_uuid_macros::impl_typed_uuid_kinds;
957///
958/// // Invoke this macro with:
959/// impl_typed_uuid_kinds! {
960///     kinds = {
961///         User = {},
962///         Project = {},
963///         // ...
964///     },
965/// }
966/// ```
967///
968/// See [`newtype-uuid-macros`] for more information.
969///
970/// [`newtype-uuid-macros`]: https://docs.rs/newtype-uuid-macros
971/// [`JsonSchema`]: schemars::JsonSchema
972pub trait TypedUuidKind: Send + Sync + 'static {
973    /// Returns the corresponding tag for this kind.
974    ///
975    /// The tag forms a runtime representation of this type.
976    ///
977    /// The tag is required to be a static string.
978    fn tag() -> TypedUuidTag;
979
980    /// Returns a string that corresponds to a type alias for `TypedUuid<Self>`,
981    /// if one is defined.
982    ///
983    /// The type alias must be defined in the same module as `Self`. This
984    /// function is used by the schemars integration to refer to embed a
985    /// reference to that alias in the schema, if available.
986    ///
987    /// This is usually defined by the [`newtype-uuid-macros`] crate.
988    ///
989    /// [`newtype-uuid-macros`]: https://docs.rs/newtype-uuid-macros
990    #[inline]
991    fn alias() -> Option<&'static str> {
992        None
993    }
994}
995
996/// Describes what kind of [`TypedUuid`] something is.
997///
998/// This is the runtime equivalent of [`TypedUuidKind`].
999#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1000pub struct TypedUuidTag(&'static str);
1001
1002impl TypedUuidTag {
1003    /// Creates a new `TypedUuidTag` from a static string.
1004    ///
1005    /// The string must be non-empty, and consist of:
1006    /// - ASCII letters
1007    /// - digits (only after the first character)
1008    /// - underscores
1009    /// - hyphens (only after the first character)
1010    ///
1011    /// # Panics
1012    ///
1013    /// Panics if the above conditions aren't met. Use [`Self::try_new`] to handle errors instead.
1014    #[must_use]
1015    pub const fn new(tag: &'static str) -> Self {
1016        match Self::try_new_impl(tag) {
1017            Ok(tag) => tag,
1018            Err(message) => panic!("{}", message),
1019        }
1020    }
1021
1022    /// Attempts to create a new `TypedUuidTag` from a static string.
1023    ///
1024    /// The string must be non-empty, and consist of:
1025    /// - ASCII letters
1026    /// - digits (only after the first character)
1027    /// - underscores
1028    /// - hyphens (only after the first character)
1029    ///
1030    /// # Errors
1031    ///
1032    /// Returns a [`TagError`] if the above conditions aren't met.
1033    pub const fn try_new(tag: &'static str) -> Result<Self, TagError> {
1034        match Self::try_new_impl(tag) {
1035            Ok(tag) => Ok(tag),
1036            Err(message) => Err(TagError {
1037                input: tag,
1038                message,
1039            }),
1040        }
1041    }
1042
1043    const fn try_new_impl(tag: &'static str) -> Result<Self, &'static str> {
1044        if tag.is_empty() {
1045            return Err("tag must not be empty");
1046        }
1047
1048        let bytes = tag.as_bytes();
1049        if !(bytes[0].is_ascii_alphabetic() || bytes[0] == b'_') {
1050            return Err("first character of tag must be an ASCII letter or underscore");
1051        }
1052
1053        let mut bytes = match bytes {
1054            [_, rest @ ..] => rest,
1055            [] => panic!("already checked that it's non-empty"),
1056        };
1057        while let [rest @ .., last] = &bytes {
1058            if !(last.is_ascii_alphanumeric() || *last == b'_' || *last == b'-') {
1059                break;
1060            }
1061            bytes = rest;
1062        }
1063
1064        if !bytes.is_empty() {
1065            return Err("tag must only contain ASCII letters, digits, underscores, or hyphens");
1066        }
1067
1068        Ok(Self(tag))
1069    }
1070
1071    /// Returns the tag as a string.
1072    pub const fn as_str(&self) -> &'static str {
1073        self.0
1074    }
1075}
1076
1077impl fmt::Display for TypedUuidTag {
1078    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1079        f.write_str(self.0)
1080    }
1081}
1082
1083impl AsRef<str> for TypedUuidTag {
1084    fn as_ref(&self) -> &str {
1085        self.0
1086    }
1087}
1088
1089/// An error that occurred while creating a [`TypedUuidTag`].
1090#[derive(Clone, Debug)]
1091#[non_exhaustive]
1092pub struct TagError {
1093    /// The input string.
1094    pub input: &'static str,
1095
1096    /// The error message.
1097    pub message: &'static str,
1098}
1099
1100impl fmt::Display for TagError {
1101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1102        write!(
1103            f,
1104            "error creating tag from '{}': {}",
1105            self.input, self.message
1106        )
1107    }
1108}
1109
1110impl core::error::Error for TagError {}
1111
1112/// An error that occurred while parsing a [`TypedUuid`].
1113#[derive(Clone, Debug)]
1114#[non_exhaustive]
1115pub struct ParseError {
1116    /// The underlying error.
1117    pub error: uuid::Error,
1118
1119    /// The tag of the UUID that failed to parse.
1120    pub tag: TypedUuidTag,
1121}
1122
1123impl fmt::Display for ParseError {
1124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1125        write!(f, "error parsing UUID ({})", self.tag)
1126    }
1127}
1128
1129impl core::error::Error for ParseError {
1130    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
1131        Some(&self.error)
1132    }
1133}
1134
1135/// A trait abstracting over typed and untyped UUIDs.
1136///
1137/// This can be used to write code that's generic over [`TypedUuid`], [`Uuid`], and other types that
1138/// may wrap [`TypedUuid`] (due to e.g. orphan rules).
1139///
1140/// This trait is similar to `From`, but a bit harder to get wrong -- in general, the conversion
1141/// from and to untyped UUIDs should be careful and explicit.
1142pub trait GenericUuid {
1143    /// Creates a new instance of `Self` from an untyped [`Uuid`].
1144    #[must_use]
1145    fn from_untyped_uuid(uuid: Uuid) -> Self
1146    where
1147        Self: Sized;
1148
1149    /// Converts `self` into an untyped [`Uuid`].
1150    #[must_use]
1151    fn into_untyped_uuid(self) -> Uuid
1152    where
1153        Self: Sized;
1154
1155    /// Returns the inner [`Uuid`].
1156    ///
1157    /// Generally, [`into_untyped_uuid`](Self::into_untyped_uuid) should be preferred. However,
1158    /// in some cases it may be necessary to use this method to satisfy lifetime constraints.
1159    fn as_untyped_uuid(&self) -> &Uuid;
1160}
1161
1162impl GenericUuid for Uuid {
1163    #[inline]
1164    fn from_untyped_uuid(uuid: Uuid) -> Self {
1165        uuid
1166    }
1167
1168    #[inline]
1169    fn into_untyped_uuid(self) -> Uuid {
1170        self
1171    }
1172
1173    #[inline]
1174    fn as_untyped_uuid(&self) -> &Uuid {
1175        self
1176    }
1177}
1178
1179impl<T: TypedUuidKind> GenericUuid for TypedUuid<T> {
1180    #[inline]
1181    fn from_untyped_uuid(uuid: Uuid) -> Self {
1182        Self {
1183            uuid,
1184            _phantom: PhantomData,
1185        }
1186    }
1187
1188    #[inline]
1189    fn into_untyped_uuid(self) -> Uuid {
1190        self.uuid
1191    }
1192
1193    #[inline]
1194    fn as_untyped_uuid(&self) -> &Uuid {
1195        &self.uuid
1196    }
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201    use super::*;
1202
1203    #[test]
1204    fn test_validate_tags() {
1205        for &valid_tag in &[
1206            "a", "a-", "a_", "a-b", "a_b", "a1", "a1-", "a1_", "a1-b", "a1_b", "_a",
1207        ] {
1208            TypedUuidTag::try_new(valid_tag).expect("tag is valid");
1209            // Should not panic
1210            _ = TypedUuidTag::new(valid_tag);
1211        }
1212
1213        for invalid_tag in &["", "1", "-", "a1b!", "a1-b!", "a1_b:", "\u{1f4a9}"] {
1214            TypedUuidTag::try_new(invalid_tag).unwrap_err();
1215        }
1216    }
1217
1218    // This test just ensures that `GenericUuid` is object-safe.
1219    #[test]
1220    #[cfg(all(feature = "v4", feature = "std"))]
1221    fn test_generic_uuid_object_safe() {
1222        let uuid = Uuid::new_v4();
1223        let box_uuid = Box::new(uuid) as Box<dyn GenericUuid>;
1224        assert_eq!(box_uuid.as_untyped_uuid(), &uuid);
1225    }
1226}