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//! ```rust
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 defining a macro for your purposes. An
44//! example macro:
45//!
46//! ```rust
47//! # use newtype_uuid::{TypedUuidKind, TypedUuidTag};
48//! macro_rules! impl_typed_uuid_kind {
49//!     ($($kind:ident => $tag:literal),* $(,)?) => {
50//!         $(
51//!             pub enum $kind {}
52//!
53//!             impl TypedUuidKind for $kind {
54//!                 #[inline]
55//!                 fn tag() -> TypedUuidTag {
56//!                     const TAG: TypedUuidTag = TypedUuidTag::new($tag);
57//!                     TAG
58//!                 }
59//!             }
60//!         )*
61//!     };
62//! }
63//!
64//! // Invoke this macro with:
65//! impl_typed_uuid_kind! {
66//!     Kind1 => "kind1",
67//!     Kind2 => "kind2",
68//! }
69//! ```
70//!
71//! # Implementations
72//!
73//! In general, [`TypedUuid`] uses the same wire and serialization formats as [`Uuid`]. This means
74//! that persistent representations of [`TypedUuid`] are the same as [`Uuid`]; [`TypedUuid`] is
75//! intended to be helpful within Rust code, not across serialization boundaries.
76//!
77//! - The `Display` and `FromStr` impls are forwarded to the underlying [`Uuid`].
78//! - If the `serde` feature is enabled, `TypedUuid` will serialize and deserialize using the same
79//!   format as [`Uuid`].
80//! - If the `schemars08` feature is enabled, [`TypedUuid`] will implement `JsonSchema` if the
81//!   corresponding [`TypedUuidKind`] implements `JsonSchema`.
82//!
83//! To abstract over typed and untyped UUIDs, the [`GenericUuid`] trait is provided. This trait also
84//! permits conversions between typed and untyped UUIDs.
85//!
86//! # Dependencies
87//!
88//! - The only required dependency is the [`uuid`] crate. Optional features may add further
89//!   dependencies.
90//!
91//! # Features
92//!
93//! - `default`: Enables default features in the newtype-uuid crate.
94//! - `std`: Enables the use of the standard library. *Enabled by default.*
95//! - `serde`: Enables serialization and deserialization support via Serde. *Not enabled by
96//!   default.*
97//! - `v4`: Enables the `new_v4` method for generating UUIDs. *Not enabled by default.*
98//! - `schemars08`: Enables support for generating JSON schemas via schemars 0.8. *Not enabled by
99//!   default.* Note that the format of the generated schema is **not currently part** of the stable
100//!   API, though we hope to stabilize it in the future.
101//! - `proptest1`: Enables support for generating `proptest::Arbitrary` instances of UUIDs. *Not enabled by default.*
102//!
103//! # Minimum supported Rust version (MSRV)
104//!
105//! The MSRV of this crate is **Rust 1.67.** In general, this crate will follow the MSRV of the
106//! underlying `uuid` crate or of dependencies, with an aim to be conservative.
107//!
108//! Within the 1.x series, MSRV updates will be accompanied by a minor version bump. The MSRVs for
109//! each minor version are:
110//!
111//! * Version **1.0.x**: Rust 1.60.
112//! * Version **1.1.x**: Rust 1.61. This permits `TypedUuid<T>` to have `const fn` methods.
113//! * Version **1.2.x**: Rust 1.67, required by some dependency updates.
114//!
115//! # Alternatives
116//!
117//! - [`typed-uuid`](https://crates.io/crates/typed-uuid): generally similar, but with a few design
118//!   decisions that are different.
119
120#![forbid(unsafe_code)]
121#![warn(missing_docs)]
122#![cfg_attr(not(feature = "std"), no_std)]
123#![cfg_attr(doc_cfg, feature(doc_cfg, doc_auto_cfg))]
124
125#[cfg(feature = "alloc")]
126extern crate alloc;
127
128use core::{
129    cmp::Ordering,
130    fmt,
131    hash::{Hash, Hasher},
132    marker::PhantomData,
133    str::FromStr,
134};
135use uuid::{Uuid, Version};
136
137/// A UUID with type-level information about what it's used for.
138///
139/// For more, see [the library documentation](crate).
140#[repr(transparent)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142#[cfg_attr(feature = "serde", serde(transparent, bound = ""))]
143pub struct TypedUuid<T: TypedUuidKind> {
144    uuid: Uuid,
145    _phantom: PhantomData<T>,
146}
147
148impl<T: TypedUuidKind> TypedUuid<T> {
149    /// The 'nil UUID' (all zeros).
150    ///
151    /// The nil UUID is a special form of UUID that is specified to have all
152    /// 128 bits set to zero.
153    ///
154    /// # References
155    ///
156    /// * [Nil UUID in RFC4122](https://tools.ietf.org/html/rfc4122.html#section-4.1.7)
157    #[inline]
158    #[must_use]
159    pub const fn nil() -> Self {
160        Self {
161            uuid: Uuid::nil(),
162            _phantom: PhantomData,
163        }
164    }
165
166    /// The 'max UUID' (all ones).
167    ///
168    /// The max UUID is a special form of UUID that is specified to have all
169    /// 128 bits set to one.
170    ///
171    /// # References
172    ///
173    /// * [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)
174    #[inline]
175    #[must_use]
176    pub const fn max() -> Self {
177        Self {
178            uuid: Uuid::max(),
179            _phantom: PhantomData,
180        }
181    }
182
183    /// Creates a UUID from four field values.
184    #[inline]
185    #[must_use]
186    pub const fn from_fields(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
187        Self {
188            uuid: Uuid::from_fields(d1, d2, d3, &d4),
189            _phantom: PhantomData,
190        }
191    }
192
193    /// Creates a UUID from four field values in little-endian order.
194    ///
195    /// The bytes in the `d1`, `d2` and `d3` fields will be flipped to convert into big-endian
196    /// order. This is based on the endianness of the UUID, rather than the target environment so
197    /// bytes will be flipped on both big and little endian machines.
198    #[inline]
199    #[must_use]
200    pub const fn from_fields_le(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
201        Self {
202            uuid: Uuid::from_fields_le(d1, d2, d3, &d4),
203            _phantom: PhantomData,
204        }
205    }
206
207    /// Creates a UUID from a 128bit value.
208    #[inline]
209    #[must_use]
210    pub const fn from_u128(value: u128) -> Self {
211        Self {
212            uuid: Uuid::from_u128(value),
213            _phantom: PhantomData,
214        }
215    }
216
217    /// Creates a UUID from a 128bit value in little-endian order.
218    ///
219    /// The entire value will be flipped to convert into big-endian order. This is based on the
220    /// endianness of the UUID, rather than the target environment so bytes will be flipped on both
221    /// big and little endian machines.
222    #[inline]
223    #[must_use]
224    pub const fn from_u128_le(value: u128) -> Self {
225        Self {
226            uuid: Uuid::from_u128_le(value),
227            _phantom: PhantomData,
228        }
229    }
230
231    /// Creates a UUID from two 64bit values.
232    #[inline]
233    #[must_use]
234    pub const fn from_u64_pair(d1: u64, d2: u64) -> Self {
235        Self {
236            uuid: Uuid::from_u64_pair(d1, d2),
237            _phantom: PhantomData,
238        }
239    }
240
241    /// Creates a UUID using the supplied bytes.
242    #[inline]
243    #[must_use]
244    pub const fn from_bytes(bytes: uuid::Bytes) -> Self {
245        Self {
246            uuid: Uuid::from_bytes(bytes),
247            _phantom: PhantomData,
248        }
249    }
250
251    /// Creates a UUID using the supplied bytes in little-endian order.
252    ///
253    /// The individual fields encoded in the buffer will be flipped.
254    #[inline]
255    #[must_use]
256    pub const fn from_bytes_le(bytes: uuid::Bytes) -> Self {
257        Self {
258            uuid: Uuid::from_bytes_le(bytes),
259            _phantom: PhantomData,
260        }
261    }
262
263    /// Creates a new, random UUID v4 of this type.
264    #[inline]
265    #[cfg(feature = "v4")]
266    #[must_use]
267    pub fn new_v4() -> Self {
268        Self::from_untyped_uuid(Uuid::new_v4())
269    }
270
271    /// Returns the version number of the UUID.
272    ///
273    /// This represents the algorithm used to generate the value.
274    /// This method is the future-proof alternative to [`Self::get_version`].
275    ///
276    /// # References
277    ///
278    /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
279    #[inline]
280    pub const fn get_version_num(&self) -> usize {
281        self.uuid.get_version_num()
282    }
283
284    /// Returns the version of the UUID.
285    ///
286    /// This represents the algorithm used to generate the value.
287    /// If the version field doesn't contain a recognized version then `None`
288    /// is returned. If you're trying to read the version for a future extension
289    /// you can also use [`Uuid::get_version_num`] to unconditionally return a
290    /// number. Future extensions may start to return `Some` once they're
291    /// standardized and supported.
292    ///
293    /// # References
294    ///
295    /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
296    #[inline]
297    pub fn get_version(&self) -> Option<Version> {
298        self.uuid.get_version()
299    }
300
301    /// Converts the UUID to one with looser semantics.
302    ///
303    /// By default, UUID kinds are considered independent, and conversions
304    /// between them must happen via the [`GenericUuid`] interface. But in some
305    /// cases, there may be a relationship between two different UUID kinds, and
306    /// you may wish to easily convert UUIDs from one kind to another.
307    ///
308    /// Typically, a conversion from `TypedUuid<T>` to `TypedUuid<U>` is most
309    /// useful when `T`'s semantics are a superset of `U`'s, or in other words,
310    /// when every `TypedUuid<T>` is logically also a `TypedUuid<U>`.
311    ///
312    /// For instance:
313    ///
314    /// * Imagine you have [`TypedUuidKind`]s for different types of
315    ///   database connections, where `DbConnKind` is the general type
316    ///   and `PgConnKind` is a specific kind for Postgres.
317    /// * Since every Postgres connection is also a database connection,
318    ///   a cast from `TypedUuid<PgConnKind>` to `TypedUuid<DbConnKind>`
319    ///   makes sense.
320    /// * The inverse cast would not make sense, as a database connection may not
321    ///   necessarily be a Postgres connection.
322    ///
323    /// This interface provides an alternative, safer way to perform this
324    /// conversion. Indicate your intention to allow a conversion between kinds
325    /// by implementing `From<T> for U`, as shown in the example below.
326    ///
327    /// # Examples
328    ///
329    /// ```
330    /// use newtype_uuid::{TypedUuid, TypedUuidKind, TypedUuidTag};
331    ///
332    /// // Let's say that these UUIDs represent repositories for different
333    /// // version control systems, such that you have a generic RepoKind:
334    /// pub enum RepoKind {}
335    /// impl TypedUuidKind for RepoKind {
336    ///     fn tag() -> TypedUuidTag {
337    ///         const TAG: TypedUuidTag = TypedUuidTag::new("repo");
338    ///         TAG
339    ///     }
340    /// }
341    ///
342    /// // You also have more specific kinds:
343    /// pub enum GitRepoKind {}
344    /// impl TypedUuidKind for GitRepoKind {
345    ///     fn tag() -> TypedUuidTag {
346    ///         const TAG: TypedUuidTag = TypedUuidTag::new("git_repo");
347    ///         TAG
348    ///     }
349    /// }
350    /// // (and HgRepoKind, JujutsuRepoKind, etc...)
351    ///
352    /// // First, define a `From` impl. This impl indicates your desire
353    /// // to convert from one kind to another.
354    /// impl From<GitRepoKind> for RepoKind {
355    ///     fn from(value: GitRepoKind) -> Self {
356    ///         match value {}
357    ///     }
358    /// }
359    ///
360    /// // Now you can convert between them:
361    /// let git_uuid: TypedUuid<GitRepoKind> =
362    ///     TypedUuid::from_u128(0xe9245204_34ea_4ca7_a1c6_2e94fa49df61);
363    /// let repo_uuid: TypedUuid<RepoKind> = git_uuid.upcast();
364    /// ```
365    #[inline]
366    #[must_use]
367    pub const fn upcast<U: TypedUuidKind>(self) -> TypedUuid<U>
368    where
369        T: Into<U>,
370    {
371        TypedUuid {
372            uuid: self.uuid,
373            _phantom: PhantomData,
374        }
375    }
376}
377
378// ---
379// Trait impls
380// ---
381
382impl<T: TypedUuidKind> PartialEq for TypedUuid<T> {
383    #[inline]
384    fn eq(&self, other: &Self) -> bool {
385        self.uuid.eq(&other.uuid)
386    }
387}
388
389impl<T: TypedUuidKind> Eq for TypedUuid<T> {}
390
391impl<T: TypedUuidKind> PartialOrd for TypedUuid<T> {
392    #[inline]
393    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
394        Some(self.uuid.cmp(&other.uuid))
395    }
396}
397
398impl<T: TypedUuidKind> Ord for TypedUuid<T> {
399    #[inline]
400    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
401        self.uuid.cmp(&other.uuid)
402    }
403}
404
405impl<T: TypedUuidKind> Hash for TypedUuid<T> {
406    #[inline]
407    fn hash<H: Hasher>(&self, state: &mut H) {
408        self.uuid.hash(state);
409    }
410}
411
412impl<T: TypedUuidKind> fmt::Debug for TypedUuid<T> {
413    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414        self.uuid.fmt(f)?;
415        write!(f, " ({})", T::tag())
416    }
417}
418
419impl<T: TypedUuidKind> fmt::Display for TypedUuid<T> {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        self.uuid.fmt(f)
422    }
423}
424
425impl<T: TypedUuidKind> Clone for TypedUuid<T> {
426    #[inline]
427    fn clone(&self) -> Self {
428        *self
429    }
430}
431
432impl<T: TypedUuidKind> Copy for TypedUuid<T> {}
433
434impl<T: TypedUuidKind> FromStr for TypedUuid<T> {
435    type Err = ParseError;
436
437    fn from_str(s: &str) -> Result<Self, Self::Err> {
438        let uuid = Uuid::from_str(s).map_err(|error| ParseError {
439            error,
440            tag: T::tag(),
441        })?;
442        Ok(Self::from_untyped_uuid(uuid))
443    }
444}
445
446impl<T: TypedUuidKind> Default for TypedUuid<T> {
447    #[inline]
448    fn default() -> Self {
449        Self::from_untyped_uuid(Uuid::default())
450    }
451}
452
453impl<T: TypedUuidKind> AsRef<[u8]> for TypedUuid<T> {
454    #[inline]
455    fn as_ref(&self) -> &[u8] {
456        self.uuid.as_ref()
457    }
458}
459
460#[cfg(feature = "alloc")]
461impl<T: TypedUuidKind> From<TypedUuid<T>> for alloc::vec::Vec<u8> {
462    #[inline]
463    fn from(typed_uuid: TypedUuid<T>) -> Self {
464        typed_uuid.into_untyped_uuid().into_bytes().to_vec()
465    }
466}
467
468#[cfg(feature = "schemars08")]
469mod schemars08_imp {
470    use super::*;
471    use schemars::JsonSchema;
472
473    /// Implements `JsonSchema` for `TypedUuid<T>`, if `T` implements `JsonSchema`.
474    ///
475    /// * `schema_name` is set to `"TypedUuidFor"`, concatenated by the schema name of `T`.
476    /// * `schema_id` is set to `format!("newtype_uuid::TypedUuid<{}>", T::schema_id())`.
477    /// * `json_schema` is the same as the one for `Uuid`.
478    impl<T> JsonSchema for TypedUuid<T>
479    where
480        T: TypedUuidKind + JsonSchema,
481    {
482        #[inline]
483        fn schema_name() -> String {
484            format!("TypedUuidFor{}", T::schema_name())
485        }
486
487        #[inline]
488        fn schema_id() -> std::borrow::Cow<'static, str> {
489            std::borrow::Cow::Owned(format!("newtype_uuid::TypedUuid<{}>", T::schema_id()))
490        }
491
492        #[inline]
493        fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
494            Uuid::json_schema(gen)
495        }
496    }
497}
498
499#[cfg(feature = "proptest1")]
500mod proptest1_imp {
501    use super::*;
502    use proptest::{
503        arbitrary::{any, Arbitrary},
504        strategy::{BoxedStrategy, Strategy},
505    };
506
507    /// Parameters for use with `proptest` instances.
508    ///
509    /// This is currently not exported as a type because it has no options. But
510    /// it's left in as an extension point for the future.
511    #[derive(Clone, Debug, Default)]
512    pub struct TypedUuidParams(());
513
514    /// Generates random `TypedUuid<T>` instances.
515    ///
516    /// Currently, this always returns a version 4 UUID. Support for other kinds
517    /// of UUIDs might be added via [`Self::Parameters`] in the future.
518    impl<T> Arbitrary for TypedUuid<T>
519    where
520        T: TypedUuidKind,
521    {
522        type Parameters = TypedUuidParams;
523        type Strategy = BoxedStrategy<Self>;
524
525        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
526            let bytes = any::<[u8; 16]>();
527            bytes
528                .prop_map(|b| {
529                    let uuid = uuid::Builder::from_random_bytes(b).into_uuid();
530                    TypedUuid::<T>::from_untyped_uuid(uuid)
531                })
532                .boxed()
533        }
534    }
535}
536
537/// Represents marker types that can be used as a type parameter for [`TypedUuid`].
538///
539/// Generally, an implementation of this will be a zero-sized type that can never be constructed. An
540/// empty struct or enum works well for this.
541///
542/// # Implementations
543///
544/// If the `schemars08` feature is enabled, and [`JsonSchema`] is implemented for a kind `T`, then
545/// [`TypedUuid`]`<T>` will also implement [`JsonSchema`].
546///
547/// # Notes
548///
549/// If you have a large number of UUID kinds, it can be repetitive to implement this trait for each
550/// kind. Here's a template for a macro that can help:
551///
552/// ```
553/// use newtype_uuid::{TypedUuidKind, TypedUuidTag};
554///
555/// macro_rules! impl_typed_uuid_kind {
556///     ($($kind:ident => $tag:literal),* $(,)?) => {
557///         $(
558///             pub enum $kind {}
559///
560///             impl TypedUuidKind for $kind {
561///                 #[inline]
562///                 fn tag() -> TypedUuidTag {
563///                     const TAG: TypedUuidTag = TypedUuidTag::new($tag);
564///                     TAG
565///                 }
566///             }
567///         )*
568///     };
569/// }
570///
571/// // Invoke this macro with:
572/// impl_typed_uuid_kind! {
573///     Kind1 => "kind1",
574///     Kind2 => "kind2",
575/// }
576/// ```
577///
578/// [`JsonSchema`]: schemars::JsonSchema
579pub trait TypedUuidKind: Send + Sync + 'static {
580    /// Returns the corresponding tag for this kind.
581    ///
582    /// The tag forms a runtime representation of this type.
583    ///
584    /// The tag is required to be a static string.
585    fn tag() -> TypedUuidTag;
586}
587
588/// Describes what kind of [`TypedUuid`] something is.
589///
590/// This is the runtime equivalent of [`TypedUuidKind`].
591#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
592pub struct TypedUuidTag(&'static str);
593
594impl TypedUuidTag {
595    /// Creates a new `TypedUuidTag` from a static string.
596    ///
597    /// The string must be non-empty, and consist of:
598    /// - ASCII letters
599    /// - digits (only after the first character)
600    /// - underscores
601    /// - hyphens (only after the first character)
602    ///
603    /// # Panics
604    ///
605    /// Panics if the above conditions aren't met. Use [`Self::try_new`] to handle errors instead.
606    #[must_use]
607    pub const fn new(tag: &'static str) -> Self {
608        match Self::try_new_impl(tag) {
609            Ok(tag) => tag,
610            Err(message) => panic!("{}", message),
611        }
612    }
613
614    /// Attempts to create a new `TypedUuidTag` from a static string.
615    ///
616    /// The string must be non-empty, and consist of:
617    /// - ASCII letters
618    /// - digits (only after the first character)
619    /// - underscores
620    /// - hyphens (only after the first character)
621    ///
622    /// # Errors
623    ///
624    /// Returns a [`TagError`] if the above conditions aren't met.
625    pub const fn try_new(tag: &'static str) -> Result<Self, TagError> {
626        match Self::try_new_impl(tag) {
627            Ok(tag) => Ok(tag),
628            Err(message) => Err(TagError {
629                input: tag,
630                message,
631            }),
632        }
633    }
634
635    const fn try_new_impl(tag: &'static str) -> Result<Self, &'static str> {
636        if tag.is_empty() {
637            return Err("tag must not be empty");
638        }
639
640        let bytes = tag.as_bytes();
641        if !(bytes[0].is_ascii_alphabetic() || bytes[0] == b'_') {
642            return Err("first character of tag must be an ASCII letter or underscore");
643        }
644
645        let mut bytes = match bytes {
646            [_, rest @ ..] => rest,
647            [] => panic!("already checked that it's non-empty"),
648        };
649        while let [rest @ .., last] = &bytes {
650            if !(last.is_ascii_alphanumeric() || *last == b'_' || *last == b'-') {
651                break;
652            }
653            bytes = rest;
654        }
655
656        if !bytes.is_empty() {
657            return Err("tag must only contain ASCII letters, digits, underscores, or hyphens");
658        }
659
660        Ok(Self(tag))
661    }
662
663    /// Returns the tag as a string.
664    pub const fn as_str(&self) -> &'static str {
665        self.0
666    }
667}
668
669impl fmt::Display for TypedUuidTag {
670    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
671        f.write_str(self.0)
672    }
673}
674
675impl AsRef<str> for TypedUuidTag {
676    fn as_ref(&self) -> &str {
677        self.0
678    }
679}
680
681/// An error that occurred while creating a [`TypedUuidTag`].
682#[derive(Clone, Debug)]
683#[non_exhaustive]
684pub struct TagError {
685    /// The input string.
686    pub input: &'static str,
687
688    /// The error message.
689    pub message: &'static str,
690}
691
692impl fmt::Display for TagError {
693    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
694        write!(
695            f,
696            "error creating tag from '{}': {}",
697            self.input, self.message
698        )
699    }
700}
701
702#[cfg(feature = "std")]
703impl std::error::Error for TagError {}
704
705/// An error that occurred while parsing a [`TypedUuid`].
706#[derive(Clone, Debug)]
707#[non_exhaustive]
708pub struct ParseError {
709    /// The underlying error.
710    pub error: uuid::Error,
711
712    /// The tag of the UUID that failed to parse.
713    pub tag: TypedUuidTag,
714}
715
716impl fmt::Display for ParseError {
717    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
718        write!(f, "error parsing UUID ({})", self.tag)
719    }
720}
721
722#[cfg(feature = "std")]
723impl std::error::Error for ParseError {
724    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
725        Some(&self.error)
726    }
727}
728
729/// A trait abstracting over typed and untyped UUIDs.
730///
731/// This can be used to write code that's generic over [`TypedUuid`], [`Uuid`], and other types that
732/// may wrap [`TypedUuid`] (due to e.g. orphan rules).
733///
734/// This trait is similar to `From`, but a bit harder to get wrong -- in general, the conversion
735/// from and to untyped UUIDs should be careful and explicit.
736pub trait GenericUuid {
737    /// Creates a new instance of `Self` from an untyped [`Uuid`].
738    #[must_use]
739    fn from_untyped_uuid(uuid: Uuid) -> Self
740    where
741        Self: Sized;
742
743    /// Converts `self` into an untyped [`Uuid`].
744    #[must_use]
745    fn into_untyped_uuid(self) -> Uuid
746    where
747        Self: Sized;
748
749    /// Returns the inner [`Uuid`].
750    ///
751    /// Generally, [`into_untyped_uuid`](Self::into_untyped_uuid) should be preferred. However,
752    /// in some cases it may be necessary to use this method to satisfy lifetime constraints.
753    fn as_untyped_uuid(&self) -> &Uuid;
754}
755
756impl GenericUuid for Uuid {
757    #[inline]
758    fn from_untyped_uuid(uuid: Uuid) -> Self {
759        uuid
760    }
761
762    #[inline]
763    fn into_untyped_uuid(self) -> Uuid {
764        self
765    }
766
767    #[inline]
768    fn as_untyped_uuid(&self) -> &Uuid {
769        self
770    }
771}
772
773impl<T: TypedUuidKind> GenericUuid for TypedUuid<T> {
774    #[inline]
775    fn from_untyped_uuid(uuid: Uuid) -> Self {
776        Self {
777            uuid,
778            _phantom: PhantomData,
779        }
780    }
781
782    #[inline]
783    fn into_untyped_uuid(self) -> Uuid {
784        self.uuid
785    }
786
787    #[inline]
788    fn as_untyped_uuid(&self) -> &Uuid {
789        &self.uuid
790    }
791}
792
793#[cfg(test)]
794mod tests {
795    use super::*;
796
797    #[test]
798    fn test_validate_tags() {
799        for &valid_tag in &[
800            "a", "a-", "a_", "a-b", "a_b", "a1", "a1-", "a1_", "a1-b", "a1_b", "_a",
801        ] {
802            TypedUuidTag::try_new(valid_tag).expect("tag is valid");
803            // Should not panic
804            _ = TypedUuidTag::new(valid_tag);
805        }
806
807        for invalid_tag in &["", "1", "-", "a1b!", "a1-b!", "a1_b:", "\u{1f4a9}"] {
808            TypedUuidTag::try_new(invalid_tag).unwrap_err();
809        }
810    }
811
812    // This test just ensures that `GenericUuid` is object-safe.
813    #[test]
814    #[cfg(all(feature = "v4", feature = "std"))]
815    fn test_generic_uuid_object_safe() {
816        let uuid = Uuid::new_v4();
817        let box_uuid = Box::new(uuid) as Box<dyn GenericUuid>;
818        assert_eq!(box_uuid.as_untyped_uuid(), &uuid);
819    }
820}