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.79.** 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//!
136//! # Alternatives
137//!
138//! - [`typed-uuid`](https://crates.io/crates/typed-uuid): generally similar, but with a few design
139//! decisions that are different.
140
141#![forbid(unsafe_code)]
142#![warn(missing_docs)]
143#![cfg_attr(not(feature = "std"), no_std)]
144#![cfg_attr(doc_cfg, feature(doc_cfg, doc_auto_cfg))]
145
146#[cfg(feature = "alloc")]
147extern crate alloc;
148
149/// Macro support for [`newtype-uuid-macros`].
150///
151/// This module re-exports types needed for [`newtype-uuid-macros`] to work.
152///
153/// [`newtype-uuid-macros`]: https://docs.rs/newtype-uuid-macros
154#[doc(hidden)]
155pub mod macro_support {
156 #[cfg(feature = "schemars08")]
157 pub use schemars as schemars08;
158 #[cfg(feature = "schemars08")]
159 pub use serde_json;
160}
161
162use core::{
163 cmp::Ordering,
164 fmt,
165 hash::{Hash, Hasher},
166 marker::PhantomData,
167 str::FromStr,
168};
169use uuid::{Uuid, Version};
170
171/// A UUID with type-level information about what it's used for.
172///
173/// For more, see [the library documentation](crate).
174#[repr(transparent)]
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176#[cfg_attr(feature = "serde", serde(transparent, bound = ""))]
177pub struct TypedUuid<T: TypedUuidKind> {
178 uuid: Uuid,
179 _phantom: PhantomData<T>,
180}
181
182impl<T: TypedUuidKind> TypedUuid<T> {
183 /// The 'nil UUID' (all zeros).
184 ///
185 /// The nil UUID is a special form of UUID that is specified to have all
186 /// 128 bits set to zero.
187 ///
188 /// # References
189 ///
190 /// * [Nil UUID in RFC4122](https://tools.ietf.org/html/rfc4122.html#section-4.1.7)
191 #[inline]
192 #[must_use]
193 pub const fn nil() -> Self {
194 Self {
195 uuid: Uuid::nil(),
196 _phantom: PhantomData,
197 }
198 }
199
200 /// The 'max UUID' (all ones).
201 ///
202 /// The max UUID is a special form of UUID that is specified to have all
203 /// 128 bits set to one.
204 ///
205 /// # References
206 ///
207 /// * [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)
208 #[inline]
209 #[must_use]
210 pub const fn max() -> Self {
211 Self {
212 uuid: Uuid::max(),
213 _phantom: PhantomData,
214 }
215 }
216
217 /// Creates a UUID from four field values.
218 #[inline]
219 #[must_use]
220 pub const fn from_fields(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
221 Self {
222 uuid: Uuid::from_fields(d1, d2, d3, &d4),
223 _phantom: PhantomData,
224 }
225 }
226
227 /// Creates a UUID from four field values in little-endian order.
228 ///
229 /// The bytes in the `d1`, `d2` and `d3` fields will be flipped to convert into big-endian
230 /// order. This is based on the endianness of the UUID, rather than the target environment so
231 /// bytes will be flipped on both big and little endian machines.
232 #[inline]
233 #[must_use]
234 pub const fn from_fields_le(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
235 Self {
236 uuid: Uuid::from_fields_le(d1, d2, d3, &d4),
237 _phantom: PhantomData,
238 }
239 }
240
241 /// Creates a UUID from a 128bit value.
242 #[inline]
243 #[must_use]
244 pub const fn from_u128(value: u128) -> Self {
245 Self {
246 uuid: Uuid::from_u128(value),
247 _phantom: PhantomData,
248 }
249 }
250
251 /// Creates a UUID from a 128bit value in little-endian order.
252 ///
253 /// The entire value will be flipped to convert into big-endian order. This is based on the
254 /// endianness of the UUID, rather than the target environment so bytes will be flipped on both
255 /// big and little endian machines.
256 #[inline]
257 #[must_use]
258 pub const fn from_u128_le(value: u128) -> Self {
259 Self {
260 uuid: Uuid::from_u128_le(value),
261 _phantom: PhantomData,
262 }
263 }
264
265 /// Creates a UUID from two 64bit values.
266 #[inline]
267 #[must_use]
268 pub const fn from_u64_pair(d1: u64, d2: u64) -> Self {
269 Self {
270 uuid: Uuid::from_u64_pair(d1, d2),
271 _phantom: PhantomData,
272 }
273 }
274
275 /// Creates a UUID using the supplied bytes.
276 #[inline]
277 #[must_use]
278 pub const fn from_bytes(bytes: uuid::Bytes) -> Self {
279 Self {
280 uuid: Uuid::from_bytes(bytes),
281 _phantom: PhantomData,
282 }
283 }
284
285 /// Creates a UUID using the supplied bytes in little-endian order.
286 ///
287 /// The individual fields encoded in the buffer will be flipped.
288 #[inline]
289 #[must_use]
290 pub const fn from_bytes_le(bytes: uuid::Bytes) -> Self {
291 Self {
292 uuid: Uuid::from_bytes_le(bytes),
293 _phantom: PhantomData,
294 }
295 }
296
297 /// Creates a new, random UUID v4 of this type.
298 #[inline]
299 #[cfg(feature = "v4")]
300 #[must_use]
301 pub fn new_v4() -> Self {
302 Self::from_untyped_uuid(Uuid::new_v4())
303 }
304
305 /// Returns the version number of the UUID.
306 ///
307 /// This represents the algorithm used to generate the value.
308 /// This method is the future-proof alternative to [`Self::get_version`].
309 ///
310 /// # References
311 ///
312 /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
313 #[inline]
314 pub const fn get_version_num(&self) -> usize {
315 self.uuid.get_version_num()
316 }
317
318 /// Returns the version of the UUID.
319 ///
320 /// This represents the algorithm used to generate the value.
321 /// If the version field doesn't contain a recognized version then `None`
322 /// is returned. If you're trying to read the version for a future extension
323 /// you can also use [`Uuid::get_version_num`] to unconditionally return a
324 /// number. Future extensions may start to return `Some` once they're
325 /// standardized and supported.
326 ///
327 /// # References
328 ///
329 /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
330 #[inline]
331 pub fn get_version(&self) -> Option<Version> {
332 self.uuid.get_version()
333 }
334
335 /// Converts the UUID to one with looser semantics.
336 ///
337 /// By default, UUID kinds are considered independent, and conversions
338 /// between them must happen via the [`GenericUuid`] interface. But in some
339 /// cases, there may be a relationship between two different UUID kinds, and
340 /// you may wish to easily convert UUIDs from one kind to another.
341 ///
342 /// Typically, a conversion from `TypedUuid<T>` to `TypedUuid<U>` is most
343 /// useful when `T`'s semantics are a superset of `U`'s, or in other words,
344 /// when every `TypedUuid<T>` is logically also a `TypedUuid<U>`.
345 ///
346 /// For instance:
347 ///
348 /// * Imagine you have [`TypedUuidKind`]s for different types of
349 /// database connections, where `DbConnKind` is the general type
350 /// and `PgConnKind` is a specific kind for Postgres.
351 /// * Since every Postgres connection is also a database connection,
352 /// a cast from `TypedUuid<PgConnKind>` to `TypedUuid<DbConnKind>`
353 /// makes sense.
354 /// * The inverse cast would not make sense, as a database connection may not
355 /// necessarily be a Postgres connection.
356 ///
357 /// This interface provides an alternative, safer way to perform this
358 /// conversion. Indicate your intention to allow a conversion between kinds
359 /// by implementing `From<T> for U`, as shown in the example below.
360 ///
361 /// # Examples
362 ///
363 /// ```
364 /// use newtype_uuid::{TypedUuid, TypedUuidKind, TypedUuidTag};
365 ///
366 /// // Let's say that these UUIDs represent repositories for different
367 /// // version control systems, such that you have a generic RepoKind:
368 /// pub enum RepoKind {}
369 /// impl TypedUuidKind for RepoKind {
370 /// fn tag() -> TypedUuidTag {
371 /// const TAG: TypedUuidTag = TypedUuidTag::new("repo");
372 /// TAG
373 /// }
374 /// }
375 ///
376 /// // You also have more specific kinds:
377 /// pub enum GitRepoKind {}
378 /// impl TypedUuidKind for GitRepoKind {
379 /// fn tag() -> TypedUuidTag {
380 /// const TAG: TypedUuidTag = TypedUuidTag::new("git_repo");
381 /// TAG
382 /// }
383 /// }
384 /// // (and HgRepoKind, JujutsuRepoKind, etc...)
385 ///
386 /// // First, define a `From` impl. This impl indicates your desire
387 /// // to convert from one kind to another.
388 /// impl From<GitRepoKind> for RepoKind {
389 /// fn from(value: GitRepoKind) -> Self {
390 /// match value {}
391 /// }
392 /// }
393 ///
394 /// // Now you can convert between them:
395 /// let git_uuid: TypedUuid<GitRepoKind> =
396 /// TypedUuid::from_u128(0xe9245204_34ea_4ca7_a1c6_2e94fa49df61);
397 /// let repo_uuid: TypedUuid<RepoKind> = git_uuid.upcast();
398 /// ```
399 #[inline]
400 #[must_use]
401 pub const fn upcast<U: TypedUuidKind>(self) -> TypedUuid<U>
402 where
403 T: Into<U>,
404 {
405 TypedUuid {
406 uuid: self.uuid,
407 _phantom: PhantomData,
408 }
409 }
410}
411
412// ---
413// Trait impls
414// ---
415
416impl<T: TypedUuidKind> PartialEq for TypedUuid<T> {
417 #[inline]
418 fn eq(&self, other: &Self) -> bool {
419 self.uuid.eq(&other.uuid)
420 }
421}
422
423impl<T: TypedUuidKind> Eq for TypedUuid<T> {}
424
425impl<T: TypedUuidKind> PartialOrd for TypedUuid<T> {
426 #[inline]
427 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
428 Some(self.cmp(other))
429 }
430}
431
432impl<T: TypedUuidKind> Ord for TypedUuid<T> {
433 #[inline]
434 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
435 self.uuid.cmp(&other.uuid)
436 }
437}
438
439impl<T: TypedUuidKind> Hash for TypedUuid<T> {
440 #[inline]
441 fn hash<H: Hasher>(&self, state: &mut H) {
442 self.uuid.hash(state);
443 }
444}
445
446impl<T: TypedUuidKind> fmt::Debug for TypedUuid<T> {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 self.uuid.fmt(f)?;
449 write!(f, " ({})", T::tag())
450 }
451}
452
453impl<T: TypedUuidKind> fmt::Display for TypedUuid<T> {
454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455 self.uuid.fmt(f)
456 }
457}
458
459impl<T: TypedUuidKind> Clone for TypedUuid<T> {
460 #[inline]
461 fn clone(&self) -> Self {
462 *self
463 }
464}
465
466impl<T: TypedUuidKind> Copy for TypedUuid<T> {}
467
468impl<T: TypedUuidKind> FromStr for TypedUuid<T> {
469 type Err = ParseError;
470
471 fn from_str(s: &str) -> Result<Self, Self::Err> {
472 let uuid = Uuid::from_str(s).map_err(|error| ParseError {
473 error,
474 tag: T::tag(),
475 })?;
476 Ok(Self::from_untyped_uuid(uuid))
477 }
478}
479
480impl<T: TypedUuidKind> Default for TypedUuid<T> {
481 #[inline]
482 fn default() -> Self {
483 Self::from_untyped_uuid(Uuid::default())
484 }
485}
486
487impl<T: TypedUuidKind> AsRef<[u8]> for TypedUuid<T> {
488 #[inline]
489 fn as_ref(&self) -> &[u8] {
490 self.uuid.as_ref()
491 }
492}
493
494#[cfg(feature = "alloc")]
495impl<T: TypedUuidKind> From<TypedUuid<T>> for alloc::vec::Vec<u8> {
496 #[inline]
497 fn from(typed_uuid: TypedUuid<T>) -> Self {
498 typed_uuid.into_untyped_uuid().into_bytes().to_vec()
499 }
500}
501
502#[cfg(feature = "schemars08")]
503mod schemars08_imp {
504 use super::*;
505 use schemars::{
506 schema::{InstanceType, Schema, SchemaObject},
507 schema_for, JsonSchema, SchemaGenerator,
508 };
509
510 const CRATE_NAME: &str = "newtype-uuid";
511 const CRATE_VERSION: &str = "1";
512 const CRATE_PATH: &str = "newtype_uuid::TypedUuid";
513
514 /// Implements `JsonSchema` for `TypedUuid<T>`, if `T` implements `JsonSchema`.
515 ///
516 /// * `schema_name` is set to `"TypedUuidFor"`, concatenated by the schema name of `T`.
517 /// * `schema_id` is set to `format!("newtype_uuid::TypedUuid<{}>", T::schema_id())`.
518 /// * `json_schema` is the same as the one for `Uuid`, with the `x-rust-type` extension
519 /// to allow automatic replacement in typify and progenitor.
520 impl<T> JsonSchema for TypedUuid<T>
521 where
522 T: TypedUuidKind + JsonSchema,
523 {
524 #[inline]
525 fn schema_name() -> String {
526 // Use the alias if available, otherwise generate our own schema name.
527 if let Some(alias) = T::alias() {
528 alias.to_owned()
529 } else {
530 format!("TypedUuidFor{}", T::schema_name())
531 }
532 }
533
534 #[inline]
535 fn schema_id() -> std::borrow::Cow<'static, str> {
536 std::borrow::Cow::Owned(format!("newtype_uuid::TypedUuid<{}>", T::schema_id()))
537 }
538
539 #[inline]
540 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
541 // Look at the schema for `T`. If it has `x-rust-type`, *and* if an
542 // alias is available, we can lift up the `x-rust-type` into our own schema.
543 //
544 // We use a new schema generator for `T` to avoid T's schema being
545 // added to the list of schemas in `generator` in case the lifting
546 // is successful.
547 let t_schema = schema_for!(T);
548 if let Some(schema) = lift_json_schema(&t_schema.schema, T::alias()) {
549 return schema.into();
550 }
551
552 SchemaObject {
553 instance_type: Some(InstanceType::String.into()),
554 format: Some("uuid".to_string()),
555 extensions: [(
556 "x-rust-type".to_string(),
557 serde_json::json!({
558 "crate": CRATE_NAME,
559 "version": CRATE_VERSION,
560 "path": CRATE_PATH,
561 "parameters": [generator.subschema_for::<T>()]
562 }),
563 )]
564 .into_iter()
565 .collect(),
566 ..Default::default()
567 }
568 .into()
569 }
570 }
571
572 // ? on Option is too easy to make mistakes with, so we use `let Some(..) =
573 // .. else` instead.
574 #[allow(clippy::question_mark)]
575 fn lift_json_schema(schema: &SchemaObject, alias: Option<&str>) -> Option<SchemaObject> {
576 let Some(alias) = alias else {
577 return None;
578 };
579
580 let Some(v) = schema.extensions.get("x-rust-type") else {
581 return None;
582 };
583
584 // The crate, version and path must all be present.
585 let Some(crate_) = v.get("crate") else {
586 return None;
587 };
588 let Some(version) = v.get("version") else {
589 return None;
590 };
591 let Some(path) = v.get("path").and_then(|p| p.as_str()) else {
592 return None;
593 };
594 let Some((module_path, _)) = path.rsplit_once("::") else {
595 return None;
596 };
597
598 // The preconditions are all met. We can lift the schema by appending
599 // the alias to the module path.
600 let alias_path = format!("{module_path}::{alias}");
601
602 Some(SchemaObject {
603 instance_type: Some(InstanceType::String.into()),
604 format: Some("uuid".to_string()),
605 extensions: [(
606 "x-rust-type".to_string(),
607 serde_json::json!({
608 "crate": crate_,
609 "version": version,
610 "path": alias_path,
611 }),
612 )]
613 .into_iter()
614 .collect(),
615 ..Default::default()
616 })
617 }
618}
619
620#[cfg(feature = "proptest1")]
621mod proptest1_imp {
622 use super::*;
623 use proptest::{
624 arbitrary::{any, Arbitrary},
625 strategy::{BoxedStrategy, Strategy},
626 };
627
628 /// Parameters for use with `proptest` instances.
629 ///
630 /// This is currently not exported as a type because it has no options. But
631 /// it's left in as an extension point for the future.
632 #[derive(Clone, Debug, Default)]
633 pub struct TypedUuidParams(());
634
635 /// Generates random `TypedUuid<T>` instances.
636 ///
637 /// Currently, this always returns a version 4 UUID. Support for other kinds
638 /// of UUIDs might be added via [`Self::Parameters`] in the future.
639 impl<T> Arbitrary for TypedUuid<T>
640 where
641 T: TypedUuidKind,
642 {
643 type Parameters = TypedUuidParams;
644 type Strategy = BoxedStrategy<Self>;
645
646 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
647 let bytes = any::<[u8; 16]>();
648 bytes
649 .prop_map(|b| {
650 let uuid = uuid::Builder::from_random_bytes(b).into_uuid();
651 TypedUuid::<T>::from_untyped_uuid(uuid)
652 })
653 .boxed()
654 }
655 }
656}
657
658/// Represents marker types that can be used as a type parameter for [`TypedUuid`].
659///
660/// Generally, an implementation of this will be a zero-sized type that can never be constructed. An
661/// empty struct or enum works well for this.
662///
663/// # Implementations
664///
665/// If the `schemars08` feature is enabled, and [`JsonSchema`] is implemented for a kind `T`, then
666/// [`TypedUuid`]`<T>` will also implement [`JsonSchema`].
667///
668/// If you have a large number of UUID kinds, consider using
669/// [`newtype-uuid-macros`] which comes with several convenience features.
670///
671/// ```
672/// use newtype_uuid_macros::impl_typed_uuid_kinds;
673///
674/// // Invoke this macro with:
675/// impl_typed_uuid_kinds! {
676/// kinds = {
677/// User = {},
678/// Project = {},
679/// // ...
680/// },
681/// }
682/// ```
683///
684/// See [`newtype-uuid-macros`] for more information.
685///
686/// [`newtype-uuid-macros`]: https://docs.rs/newtype-uuid-macros
687/// [`JsonSchema`]: schemars::JsonSchema
688pub trait TypedUuidKind: Send + Sync + 'static {
689 /// Returns the corresponding tag for this kind.
690 ///
691 /// The tag forms a runtime representation of this type.
692 ///
693 /// The tag is required to be a static string.
694 fn tag() -> TypedUuidTag;
695
696 /// Returns a string that corresponds to a type alias for `TypedUuid<Self>`,
697 /// if one is defined.
698 ///
699 /// The type alias must be defined in the same module as `Self`. This
700 /// function is used by the schemars integration to refer to embed a
701 /// reference to that alias in the schema, if available.
702 ///
703 /// This is usually defined by the [`newtype-uuid-macros`] crate.
704 ///
705 /// [`newtype-uuid-macros`]: https://docs.rs/newtype-uuid-macros
706 #[inline]
707 fn alias() -> Option<&'static str> {
708 None
709 }
710}
711
712/// Describes what kind of [`TypedUuid`] something is.
713///
714/// This is the runtime equivalent of [`TypedUuidKind`].
715#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
716pub struct TypedUuidTag(&'static str);
717
718impl TypedUuidTag {
719 /// Creates a new `TypedUuidTag` from a static string.
720 ///
721 /// The string must be non-empty, and consist of:
722 /// - ASCII letters
723 /// - digits (only after the first character)
724 /// - underscores
725 /// - hyphens (only after the first character)
726 ///
727 /// # Panics
728 ///
729 /// Panics if the above conditions aren't met. Use [`Self::try_new`] to handle errors instead.
730 #[must_use]
731 pub const fn new(tag: &'static str) -> Self {
732 match Self::try_new_impl(tag) {
733 Ok(tag) => tag,
734 Err(message) => panic!("{}", message),
735 }
736 }
737
738 /// Attempts to create a new `TypedUuidTag` from a static string.
739 ///
740 /// The string must be non-empty, and consist of:
741 /// - ASCII letters
742 /// - digits (only after the first character)
743 /// - underscores
744 /// - hyphens (only after the first character)
745 ///
746 /// # Errors
747 ///
748 /// Returns a [`TagError`] if the above conditions aren't met.
749 pub const fn try_new(tag: &'static str) -> Result<Self, TagError> {
750 match Self::try_new_impl(tag) {
751 Ok(tag) => Ok(tag),
752 Err(message) => Err(TagError {
753 input: tag,
754 message,
755 }),
756 }
757 }
758
759 const fn try_new_impl(tag: &'static str) -> Result<Self, &'static str> {
760 if tag.is_empty() {
761 return Err("tag must not be empty");
762 }
763
764 let bytes = tag.as_bytes();
765 if !(bytes[0].is_ascii_alphabetic() || bytes[0] == b'_') {
766 return Err("first character of tag must be an ASCII letter or underscore");
767 }
768
769 let mut bytes = match bytes {
770 [_, rest @ ..] => rest,
771 [] => panic!("already checked that it's non-empty"),
772 };
773 while let [rest @ .., last] = &bytes {
774 if !(last.is_ascii_alphanumeric() || *last == b'_' || *last == b'-') {
775 break;
776 }
777 bytes = rest;
778 }
779
780 if !bytes.is_empty() {
781 return Err("tag must only contain ASCII letters, digits, underscores, or hyphens");
782 }
783
784 Ok(Self(tag))
785 }
786
787 /// Returns the tag as a string.
788 pub const fn as_str(&self) -> &'static str {
789 self.0
790 }
791}
792
793impl fmt::Display for TypedUuidTag {
794 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
795 f.write_str(self.0)
796 }
797}
798
799impl AsRef<str> for TypedUuidTag {
800 fn as_ref(&self) -> &str {
801 self.0
802 }
803}
804
805/// An error that occurred while creating a [`TypedUuidTag`].
806#[derive(Clone, Debug)]
807#[non_exhaustive]
808pub struct TagError {
809 /// The input string.
810 pub input: &'static str,
811
812 /// The error message.
813 pub message: &'static str,
814}
815
816impl fmt::Display for TagError {
817 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
818 write!(
819 f,
820 "error creating tag from '{}': {}",
821 self.input, self.message
822 )
823 }
824}
825
826#[cfg(feature = "std")]
827impl std::error::Error for TagError {}
828
829/// An error that occurred while parsing a [`TypedUuid`].
830#[derive(Clone, Debug)]
831#[non_exhaustive]
832pub struct ParseError {
833 /// The underlying error.
834 pub error: uuid::Error,
835
836 /// The tag of the UUID that failed to parse.
837 pub tag: TypedUuidTag,
838}
839
840impl fmt::Display for ParseError {
841 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
842 write!(f, "error parsing UUID ({})", self.tag)
843 }
844}
845
846#[cfg(feature = "std")]
847impl std::error::Error for ParseError {
848 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
849 Some(&self.error)
850 }
851}
852
853/// A trait abstracting over typed and untyped UUIDs.
854///
855/// This can be used to write code that's generic over [`TypedUuid`], [`Uuid`], and other types that
856/// may wrap [`TypedUuid`] (due to e.g. orphan rules).
857///
858/// This trait is similar to `From`, but a bit harder to get wrong -- in general, the conversion
859/// from and to untyped UUIDs should be careful and explicit.
860pub trait GenericUuid {
861 /// Creates a new instance of `Self` from an untyped [`Uuid`].
862 #[must_use]
863 fn from_untyped_uuid(uuid: Uuid) -> Self
864 where
865 Self: Sized;
866
867 /// Converts `self` into an untyped [`Uuid`].
868 #[must_use]
869 fn into_untyped_uuid(self) -> Uuid
870 where
871 Self: Sized;
872
873 /// Returns the inner [`Uuid`].
874 ///
875 /// Generally, [`into_untyped_uuid`](Self::into_untyped_uuid) should be preferred. However,
876 /// in some cases it may be necessary to use this method to satisfy lifetime constraints.
877 fn as_untyped_uuid(&self) -> &Uuid;
878}
879
880impl GenericUuid for Uuid {
881 #[inline]
882 fn from_untyped_uuid(uuid: Uuid) -> Self {
883 uuid
884 }
885
886 #[inline]
887 fn into_untyped_uuid(self) -> Uuid {
888 self
889 }
890
891 #[inline]
892 fn as_untyped_uuid(&self) -> &Uuid {
893 self
894 }
895}
896
897impl<T: TypedUuidKind> GenericUuid for TypedUuid<T> {
898 #[inline]
899 fn from_untyped_uuid(uuid: Uuid) -> Self {
900 Self {
901 uuid,
902 _phantom: PhantomData,
903 }
904 }
905
906 #[inline]
907 fn into_untyped_uuid(self) -> Uuid {
908 self.uuid
909 }
910
911 #[inline]
912 fn as_untyped_uuid(&self) -> &Uuid {
913 &self.uuid
914 }
915}
916
917#[cfg(test)]
918mod tests {
919 use super::*;
920
921 #[test]
922 fn test_validate_tags() {
923 for &valid_tag in &[
924 "a", "a-", "a_", "a-b", "a_b", "a1", "a1-", "a1_", "a1-b", "a1_b", "_a",
925 ] {
926 TypedUuidTag::try_new(valid_tag).expect("tag is valid");
927 // Should not panic
928 _ = TypedUuidTag::new(valid_tag);
929 }
930
931 for invalid_tag in &["", "1", "-", "a1b!", "a1-b!", "a1_b:", "\u{1f4a9}"] {
932 TypedUuidTag::try_new(invalid_tag).unwrap_err();
933 }
934 }
935
936 // This test just ensures that `GenericUuid` is object-safe.
937 #[test]
938 #[cfg(all(feature = "v4", feature = "std"))]
939 fn test_generic_uuid_object_safe() {
940 let uuid = Uuid::new_v4();
941 let box_uuid = Box::new(uuid) as Box<dyn GenericUuid>;
942 assert_eq!(box_uuid.as_untyped_uuid(), &uuid);
943 }
944}