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))]
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};
169#[cfg(feature = "v7")]
170pub use uuid::Timestamp;
171use uuid::{Uuid, Version};
172
173/// A UUID with type-level information about what it's used for.
174///
175/// For more, see [the library documentation](crate).
176#[repr(transparent)]
177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
178#[cfg_attr(feature = "serde", serde(transparent, bound = ""))]
179pub struct TypedUuid<T: TypedUuidKind> {
180 uuid: Uuid,
181 _phantom: PhantomData<T>,
182}
183
184impl<T: TypedUuidKind> TypedUuid<T> {
185 /// The 'nil UUID' (all zeros).
186 ///
187 /// The nil UUID is a special form of UUID that is specified to have all
188 /// 128 bits set to zero.
189 ///
190 /// # References
191 ///
192 /// * [Nil UUID in RFC4122](https://tools.ietf.org/html/rfc4122.html#section-4.1.7)
193 #[inline]
194 #[must_use]
195 pub const fn nil() -> Self {
196 Self {
197 uuid: Uuid::nil(),
198 _phantom: PhantomData,
199 }
200 }
201
202 /// The 'max UUID' (all ones).
203 ///
204 /// The max UUID is a special form of UUID that is specified to have all
205 /// 128 bits set to one.
206 ///
207 /// # References
208 ///
209 /// * [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)
210 #[inline]
211 #[must_use]
212 pub const fn max() -> Self {
213 Self {
214 uuid: Uuid::max(),
215 _phantom: PhantomData,
216 }
217 }
218
219 /// Creates a UUID from four field values.
220 #[inline]
221 #[must_use]
222 pub const fn from_fields(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
223 Self {
224 uuid: Uuid::from_fields(d1, d2, d3, &d4),
225 _phantom: PhantomData,
226 }
227 }
228
229 /// Creates a UUID from four field values in little-endian order.
230 ///
231 /// The bytes in the `d1`, `d2` and `d3` fields will be flipped to convert into big-endian
232 /// order. This is based on the endianness of the UUID, rather than the target environment so
233 /// bytes will be flipped on both big and little endian machines.
234 #[inline]
235 #[must_use]
236 pub const fn from_fields_le(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
237 Self {
238 uuid: Uuid::from_fields_le(d1, d2, d3, &d4),
239 _phantom: PhantomData,
240 }
241 }
242
243 /// Creates a UUID from a 128bit value.
244 #[inline]
245 #[must_use]
246 pub const fn from_u128(value: u128) -> Self {
247 Self {
248 uuid: Uuid::from_u128(value),
249 _phantom: PhantomData,
250 }
251 }
252
253 /// Creates a UUID from a 128bit value in little-endian order.
254 ///
255 /// The entire value will be flipped to convert into big-endian order. This is based on the
256 /// endianness of the UUID, rather than the target environment so bytes will be flipped on both
257 /// big and little endian machines.
258 #[inline]
259 #[must_use]
260 pub const fn from_u128_le(value: u128) -> Self {
261 Self {
262 uuid: Uuid::from_u128_le(value),
263 _phantom: PhantomData,
264 }
265 }
266
267 /// Creates a UUID from two 64bit values.
268 #[inline]
269 #[must_use]
270 pub const fn from_u64_pair(d1: u64, d2: u64) -> Self {
271 Self {
272 uuid: Uuid::from_u64_pair(d1, d2),
273 _phantom: PhantomData,
274 }
275 }
276
277 /// Creates a UUID using the supplied bytes.
278 #[inline]
279 #[must_use]
280 pub const fn from_bytes(bytes: uuid::Bytes) -> Self {
281 Self {
282 uuid: Uuid::from_bytes(bytes),
283 _phantom: PhantomData,
284 }
285 }
286
287 /// Creates a UUID using the supplied bytes in little-endian order.
288 ///
289 /// The individual fields encoded in the buffer will be flipped.
290 #[inline]
291 #[must_use]
292 pub const fn from_bytes_le(bytes: uuid::Bytes) -> Self {
293 Self {
294 uuid: Uuid::from_bytes_le(bytes),
295 _phantom: PhantomData,
296 }
297 }
298
299 /// Creates a new, random UUID v4 of this type.
300 #[inline]
301 #[cfg(feature = "v4")]
302 #[must_use]
303 pub fn new_v4() -> Self {
304 Self::from_untyped_uuid(Uuid::new_v4())
305 }
306
307 /// Creates a new, random UUID v7 of this type.
308 #[inline]
309 #[cfg(feature = "v7")]
310 #[must_use]
311 pub fn new_v7(ts: uuid::Timestamp) -> Self {
312 Self::from_untyped_uuid(Uuid::new_v7(ts))
313 }
314
315 /// Returns the version number of the UUID.
316 ///
317 /// This represents the algorithm used to generate the value.
318 /// This method is the future-proof alternative to [`Self::get_version`].
319 ///
320 /// # References
321 ///
322 /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
323 #[inline]
324 pub const fn get_version_num(&self) -> usize {
325 self.uuid.get_version_num()
326 }
327
328 /// Returns the version of the UUID.
329 ///
330 /// This represents the algorithm used to generate the value.
331 /// If the version field doesn't contain a recognized version then `None`
332 /// is returned. If you're trying to read the version for a future extension
333 /// you can also use [`Uuid::get_version_num`] to unconditionally return a
334 /// number. Future extensions may start to return `Some` once they're
335 /// standardized and supported.
336 ///
337 /// # References
338 ///
339 /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
340 #[inline]
341 pub fn get_version(&self) -> Option<Version> {
342 self.uuid.get_version()
343 }
344
345 /// Returns true if the UUID is nil (all zeros).
346 #[inline]
347 pub const fn is_nil(&self) -> bool {
348 self.uuid.is_nil()
349 }
350
351 /// Returns true if the UUID is the max value (all ones).
352 #[inline]
353 pub const fn is_max(&self) -> bool {
354 self.uuid.is_max()
355 }
356
357 /// Returns the four field values of the UUID.
358 ///
359 /// These values can be passed to [`Self::from_fields`] to reconstruct the
360 /// original UUID. The first field represents the initial eight hex digits
361 /// as a big-endian `u32`. The second and third fields represent subsequent
362 /// hex digit groups as `u16` values. The final field contains the last two
363 /// groups of hex digits as an 8-byte array.
364 ///
365 /// # Examples
366 ///
367 /// ```
368 /// # use newtype_uuid::TypedUuid;
369 /// # enum ExampleKind {}
370 /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
371 /// # fn tag() -> newtype_uuid::TypedUuidTag {
372 /// # const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
373 /// # TAG
374 /// # }
375 /// # }
376 /// let uuid: TypedUuid<ExampleKind> =
377 /// "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
378 ///
379 /// assert_eq!(
380 /// uuid.as_fields(),
381 /// (
382 /// 0xa1a2a3a4,
383 /// 0xb1b2,
384 /// 0xc1c2,
385 /// &[0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8],
386 /// )
387 /// );
388 /// ```
389 #[inline]
390 pub fn as_fields(&self) -> (u32, u16, u16, &[u8; 8]) {
391 self.uuid.as_fields()
392 }
393
394 /// Returns the four field values in little-endian order.
395 ///
396 /// The bytes within integer fields are converted from big-endian order.
397 /// This is based on the endianness of the UUID rather than the target
398 /// environment, so bytes will be flipped on both big and little endian
399 /// machines.
400 ///
401 /// # Examples
402 ///
403 /// ```
404 /// # use newtype_uuid::TypedUuid;
405 /// # enum ExampleKind {}
406 /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
407 /// # fn tag() -> newtype_uuid::TypedUuidTag {
408 /// # const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
409 /// # TAG
410 /// # }
411 /// # }
412 /// let uuid: TypedUuid<ExampleKind> =
413 /// "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
414 ///
415 /// assert_eq!(
416 /// uuid.to_fields_le(),
417 /// (
418 /// 0xa4a3a2a1,
419 /// 0xb2b1,
420 /// 0xc2c1,
421 /// &[0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8],
422 /// )
423 /// );
424 /// ```
425 #[inline]
426 pub fn to_fields_le(&self) -> (u32, u16, u16, &[u8; 8]) {
427 self.uuid.to_fields_le()
428 }
429
430 /// Returns a 128-bit value containing the UUID bytes.
431 ///
432 /// # Examples
433 ///
434 /// ```
435 /// # use newtype_uuid::TypedUuid;
436 /// # enum ExampleKind {}
437 /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
438 /// # fn tag() -> newtype_uuid::TypedUuidTag {
439 /// # const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
440 /// # TAG
441 /// # }
442 /// # }
443 /// let uuid: TypedUuid<ExampleKind> =
444 /// "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
445 ///
446 /// assert_eq!(
447 /// uuid.as_u128(),
448 /// 0xa1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8u128,
449 /// );
450 /// ```
451 #[inline]
452 pub const fn as_u128(&self) -> u128 {
453 self.uuid.as_u128()
454 }
455
456 /// Returns a 128-bit little-endian value.
457 ///
458 /// The bytes in the `u128` will be flipped to convert into big-endian order.
459 /// This is based on the endianness of the UUID, rather than the target
460 /// environment so bytes will be flipped on both big and little endian
461 /// machines.
462 ///
463 /// Note that this will produce a different result than
464 /// [`Self::to_fields_le`], because the entire UUID is reversed, rather than
465 /// reversing the individual fields in-place.
466 ///
467 /// # Examples
468 ///
469 /// ```
470 /// # use newtype_uuid::TypedUuid;
471 /// # enum ExampleKind {}
472 /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
473 /// # fn tag() -> newtype_uuid::TypedUuidTag {
474 /// # const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
475 /// # TAG
476 /// # }
477 /// # }
478 /// let uuid: TypedUuid<ExampleKind> =
479 /// "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
480 ///
481 /// assert_eq!(
482 /// uuid.to_u128_le(),
483 /// 0xd8d7d6d5d4d3d2d1c2c1b2b1a4a3a2a1u128,
484 /// );
485 /// ```
486 #[inline]
487 pub fn to_u128_le(&self) -> u128 {
488 self.uuid.to_u128_le()
489 }
490
491 /// Returns two 64-bit values representing the UUID.
492 ///
493 /// The first `u64` contains the most significant 64 bits; the second
494 /// contains the least significant bits.
495 ///
496 /// # Examples
497 ///
498 /// ```
499 /// # use newtype_uuid::TypedUuid;
500 /// # enum ExampleKind {}
501 /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
502 /// # fn tag() -> newtype_uuid::TypedUuidTag {
503 /// # const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
504 /// # TAG
505 /// # }
506 /// # }
507 /// let uuid: TypedUuid<ExampleKind> =
508 /// "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
509 ///
510 /// assert_eq!(
511 /// uuid.as_u64_pair(),
512 /// (0xa1a2a3a4b1b2c1c2, 0xd1d2d3d4d5d6d7d8),
513 /// );
514 /// ```
515 #[inline]
516 pub const fn as_u64_pair(&self) -> (u64, u64) {
517 self.uuid.as_u64_pair()
518 }
519
520 /// Returns a slice of 16 octets containing the value.
521 ///
522 /// This method borrows the underlying byte value of the UUID.
523 ///
524 /// # Examples
525 ///
526 /// ```
527 /// # use newtype_uuid::TypedUuid;
528 /// # enum ExampleKind {}
529 /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
530 /// # fn tag() -> newtype_uuid::TypedUuidTag {
531 /// # const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
532 /// # TAG
533 /// # }
534 /// # }
535 /// let bytes = [
536 /// 0xa1, 0xa2, 0xa3, 0xa4,
537 /// 0xb1, 0xb2,
538 /// 0xc1, 0xc2,
539 /// 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
540 /// ];
541 ///
542 /// let uuid = TypedUuid::<ExampleKind>::from_bytes(bytes);
543 /// let bytes2 = uuid.as_bytes();
544 ///
545 /// assert_eq!(&bytes, bytes2);
546 /// ```
547 #[inline]
548 pub const fn as_bytes(&self) -> &uuid::Bytes {
549 self.uuid.as_bytes()
550 }
551
552 /// Consumes self and returns the underlying byte value of the UUID.
553 ///
554 /// # Examples
555 ///
556 /// ```
557 /// # use newtype_uuid::TypedUuid;
558 /// # enum ExampleKind {}
559 /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
560 /// # fn tag() -> newtype_uuid::TypedUuidTag {
561 /// # const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
562 /// # TAG
563 /// # }
564 /// # }
565 /// let bytes = [
566 /// 0xa1, 0xa2, 0xa3, 0xa4,
567 /// 0xb1, 0xb2,
568 /// 0xc1, 0xc2,
569 /// 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
570 /// ];
571 ///
572 /// let uuid = TypedUuid::<ExampleKind>::from_bytes(bytes);
573 ///
574 /// assert_eq!(bytes, uuid.into_bytes());
575 /// ```
576 #[inline]
577 #[must_use]
578 pub const fn into_bytes(self) -> uuid::Bytes {
579 self.uuid.into_bytes()
580 }
581
582 /// Returns the bytes of the UUID in little-endian order.
583 ///
584 /// The bytes will be flipped to convert into little-endian order. This is
585 /// based on the endianness of the UUID, rather than the target environment
586 /// so bytes will be flipped on both big and little endian machines.
587 ///
588 /// # Examples
589 ///
590 /// ```
591 /// # use newtype_uuid::TypedUuid;
592 /// # enum ExampleKind {}
593 /// # impl newtype_uuid::TypedUuidKind for ExampleKind {
594 /// # fn tag() -> newtype_uuid::TypedUuidTag {
595 /// # const TAG: newtype_uuid::TypedUuidTag = newtype_uuid::TypedUuidTag::new("example");
596 /// # TAG
597 /// # }
598 /// # }
599 /// let uuid: TypedUuid<ExampleKind> =
600 /// "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8".parse().unwrap();
601 ///
602 /// assert_eq!(
603 /// uuid.to_bytes_le(),
604 /// [
605 /// 0xa4, 0xa3, 0xa2, 0xa1,
606 /// 0xb2, 0xb1,
607 /// 0xc2, 0xc1,
608 /// 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
609 /// ]
610 /// );
611 /// ```
612 #[inline]
613 pub fn to_bytes_le(&self) -> uuid::Bytes {
614 self.uuid.to_bytes_le()
615 }
616
617 /// Converts the UUID to one with looser semantics.
618 ///
619 /// By default, UUID kinds are considered independent, and conversions
620 /// between them must happen via the [`GenericUuid`] interface. But in some
621 /// cases, there may be a relationship between two different UUID kinds, and
622 /// you may wish to easily convert UUIDs from one kind to another.
623 ///
624 /// Typically, a conversion from `TypedUuid<T>` to `TypedUuid<U>` is most
625 /// useful when `T`'s semantics are a superset of `U`'s, or in other words,
626 /// when every `TypedUuid<T>` is logically also a `TypedUuid<U>`.
627 ///
628 /// For instance:
629 ///
630 /// * Imagine you have [`TypedUuidKind`]s for different types of
631 /// database connections, where `DbConnKind` is the general type
632 /// and `PgConnKind` is a specific kind for Postgres.
633 /// * Since every Postgres connection is also a database connection,
634 /// a cast from `TypedUuid<PgConnKind>` to `TypedUuid<DbConnKind>`
635 /// makes sense.
636 /// * The inverse cast would not make sense, as a database connection may not
637 /// necessarily be a Postgres connection.
638 ///
639 /// This interface provides an alternative, safer way to perform this
640 /// conversion. Indicate your intention to allow a conversion between kinds
641 /// by implementing `From<T> for U`, as shown in the example below.
642 ///
643 /// # Examples
644 ///
645 /// ```
646 /// use newtype_uuid::{TypedUuid, TypedUuidKind, TypedUuidTag};
647 ///
648 /// // Let's say that these UUIDs represent repositories for different
649 /// // version control systems, such that you have a generic RepoKind:
650 /// pub enum RepoKind {}
651 /// impl TypedUuidKind for RepoKind {
652 /// fn tag() -> TypedUuidTag {
653 /// const TAG: TypedUuidTag = TypedUuidTag::new("repo");
654 /// TAG
655 /// }
656 /// }
657 ///
658 /// // You also have more specific kinds:
659 /// pub enum GitRepoKind {}
660 /// impl TypedUuidKind for GitRepoKind {
661 /// fn tag() -> TypedUuidTag {
662 /// const TAG: TypedUuidTag = TypedUuidTag::new("git_repo");
663 /// TAG
664 /// }
665 /// }
666 /// // (and HgRepoKind, JujutsuRepoKind, etc...)
667 ///
668 /// // First, define a `From` impl. This impl indicates your desire
669 /// // to convert from one kind to another.
670 /// impl From<GitRepoKind> for RepoKind {
671 /// fn from(value: GitRepoKind) -> Self {
672 /// match value {}
673 /// }
674 /// }
675 ///
676 /// // Now you can convert between them:
677 /// let git_uuid: TypedUuid<GitRepoKind> =
678 /// TypedUuid::from_u128(0xe9245204_34ea_4ca7_a1c6_2e94fa49df61);
679 /// let repo_uuid: TypedUuid<RepoKind> = git_uuid.upcast();
680 /// ```
681 #[inline]
682 #[must_use]
683 pub const fn upcast<U: TypedUuidKind>(self) -> TypedUuid<U>
684 where
685 T: Into<U>,
686 {
687 TypedUuid {
688 uuid: self.uuid,
689 _phantom: PhantomData,
690 }
691 }
692}
693
694// ---
695// Trait impls
696// ---
697
698impl<T: TypedUuidKind> PartialEq for TypedUuid<T> {
699 #[inline]
700 fn eq(&self, other: &Self) -> bool {
701 self.uuid.eq(&other.uuid)
702 }
703}
704
705impl<T: TypedUuidKind> Eq for TypedUuid<T> {}
706
707impl<T: TypedUuidKind> PartialOrd for TypedUuid<T> {
708 #[inline]
709 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
710 Some(self.cmp(other))
711 }
712}
713
714impl<T: TypedUuidKind> Ord for TypedUuid<T> {
715 #[inline]
716 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
717 self.uuid.cmp(&other.uuid)
718 }
719}
720
721impl<T: TypedUuidKind> Hash for TypedUuid<T> {
722 #[inline]
723 fn hash<H: Hasher>(&self, state: &mut H) {
724 self.uuid.hash(state);
725 }
726}
727
728impl<T: TypedUuidKind> fmt::Debug for TypedUuid<T> {
729 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730 self.uuid.fmt(f)?;
731 write!(f, " ({})", T::tag())
732 }
733}
734
735impl<T: TypedUuidKind> fmt::Display for TypedUuid<T> {
736 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
737 self.uuid.fmt(f)
738 }
739}
740
741impl<T: TypedUuidKind> Clone for TypedUuid<T> {
742 #[inline]
743 fn clone(&self) -> Self {
744 *self
745 }
746}
747
748impl<T: TypedUuidKind> Copy for TypedUuid<T> {}
749
750impl<T: TypedUuidKind> FromStr for TypedUuid<T> {
751 type Err = ParseError;
752
753 fn from_str(s: &str) -> Result<Self, Self::Err> {
754 let uuid = Uuid::from_str(s).map_err(|error| ParseError {
755 error,
756 tag: T::tag(),
757 })?;
758 Ok(Self::from_untyped_uuid(uuid))
759 }
760}
761
762impl<T: TypedUuidKind> Default for TypedUuid<T> {
763 #[inline]
764 fn default() -> Self {
765 Self::from_untyped_uuid(Uuid::default())
766 }
767}
768
769impl<T: TypedUuidKind> AsRef<[u8]> for TypedUuid<T> {
770 #[inline]
771 fn as_ref(&self) -> &[u8] {
772 self.uuid.as_ref()
773 }
774}
775
776#[cfg(feature = "alloc")]
777impl<T: TypedUuidKind> From<TypedUuid<T>> for alloc::vec::Vec<u8> {
778 #[inline]
779 fn from(typed_uuid: TypedUuid<T>) -> Self {
780 typed_uuid.into_untyped_uuid().into_bytes().to_vec()
781 }
782}
783
784#[cfg(feature = "schemars08")]
785mod schemars08_imp {
786 use super::*;
787 use schemars::{
788 schema::{InstanceType, Schema, SchemaObject},
789 schema_for, JsonSchema, SchemaGenerator,
790 };
791
792 const CRATE_NAME: &str = "newtype-uuid";
793 const CRATE_VERSION: &str = "1";
794 const CRATE_PATH: &str = "newtype_uuid::TypedUuid";
795
796 /// Implements `JsonSchema` for `TypedUuid<T>`, if `T` implements `JsonSchema`.
797 ///
798 /// * `schema_name` is set to `"TypedUuidFor"`, concatenated by the schema name of `T`.
799 /// * `schema_id` is set to `format!("newtype_uuid::TypedUuid<{}>", T::schema_id())`.
800 /// * `json_schema` is the same as the one for `Uuid`, with the `x-rust-type` extension
801 /// to allow automatic replacement in typify and progenitor.
802 impl<T> JsonSchema for TypedUuid<T>
803 where
804 T: TypedUuidKind + JsonSchema,
805 {
806 #[inline]
807 fn schema_name() -> String {
808 // Use the alias if available, otherwise generate our own schema name.
809 if let Some(alias) = T::alias() {
810 alias.to_owned()
811 } else {
812 format!("TypedUuidFor{}", T::schema_name())
813 }
814 }
815
816 #[inline]
817 fn schema_id() -> std::borrow::Cow<'static, str> {
818 std::borrow::Cow::Owned(format!("newtype_uuid::TypedUuid<{}>", T::schema_id()))
819 }
820
821 #[inline]
822 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
823 // Look at the schema for `T`. If it has `x-rust-type`, *and* if an
824 // alias is available, we can lift up the `x-rust-type` into our own schema.
825 //
826 // We use a new schema generator for `T` to avoid T's schema being
827 // added to the list of schemas in `generator` in case the lifting
828 // is successful.
829 let t_schema = schema_for!(T);
830 if let Some(schema) = lift_json_schema(&t_schema.schema, T::alias()) {
831 return schema.into();
832 }
833
834 SchemaObject {
835 instance_type: Some(InstanceType::String.into()),
836 format: Some("uuid".to_string()),
837 extensions: [(
838 "x-rust-type".to_string(),
839 serde_json::json!({
840 "crate": CRATE_NAME,
841 "version": CRATE_VERSION,
842 "path": CRATE_PATH,
843 "parameters": [generator.subschema_for::<T>()]
844 }),
845 )]
846 .into_iter()
847 .collect(),
848 ..Default::default()
849 }
850 .into()
851 }
852 }
853
854 // ? on Option is too easy to make mistakes with, so we use `let Some(..) =
855 // .. else` instead.
856 #[allow(clippy::question_mark)]
857 fn lift_json_schema(schema: &SchemaObject, alias: Option<&str>) -> Option<SchemaObject> {
858 let Some(alias) = alias else {
859 return None;
860 };
861
862 let Some(v) = schema.extensions.get("x-rust-type") else {
863 return None;
864 };
865
866 // The crate, version and path must all be present.
867 let Some(crate_) = v.get("crate") else {
868 return None;
869 };
870 let Some(version) = v.get("version") else {
871 return None;
872 };
873 let Some(path) = v.get("path").and_then(|p| p.as_str()) else {
874 return None;
875 };
876 let Some((module_path, _)) = path.rsplit_once("::") else {
877 return None;
878 };
879
880 // The preconditions are all met. We can lift the schema by appending
881 // the alias to the module path.
882 let alias_path = format!("{module_path}::{alias}");
883
884 Some(SchemaObject {
885 instance_type: Some(InstanceType::String.into()),
886 format: Some("uuid".to_string()),
887 extensions: [(
888 "x-rust-type".to_string(),
889 serde_json::json!({
890 "crate": crate_,
891 "version": version,
892 "path": alias_path,
893 }),
894 )]
895 .into_iter()
896 .collect(),
897 ..Default::default()
898 })
899 }
900}
901
902#[cfg(feature = "proptest1")]
903mod proptest1_imp {
904 use super::*;
905 use proptest::{
906 arbitrary::{any, Arbitrary},
907 strategy::{BoxedStrategy, Strategy},
908 };
909
910 /// Parameters for use with `proptest` instances.
911 ///
912 /// This is currently not exported as a type because it has no options. But
913 /// it's left in as an extension point for the future.
914 #[derive(Clone, Debug, Default)]
915 pub struct TypedUuidParams(());
916
917 /// Generates random `TypedUuid<T>` instances.
918 ///
919 /// Currently, this always returns a version 4 UUID. Support for other kinds
920 /// of UUIDs might be added via [`Self::Parameters`] in the future.
921 impl<T> Arbitrary for TypedUuid<T>
922 where
923 T: TypedUuidKind,
924 {
925 type Parameters = TypedUuidParams;
926 type Strategy = BoxedStrategy<Self>;
927
928 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
929 let bytes = any::<[u8; 16]>();
930 bytes
931 .prop_map(|b| {
932 let uuid = uuid::Builder::from_random_bytes(b).into_uuid();
933 TypedUuid::<T>::from_untyped_uuid(uuid)
934 })
935 .boxed()
936 }
937 }
938}
939
940/// Represents marker types that can be used as a type parameter for [`TypedUuid`].
941///
942/// Generally, an implementation of this will be a zero-sized type that can never be constructed. An
943/// empty struct or enum works well for this.
944///
945/// # Implementations
946///
947/// If the `schemars08` feature is enabled, and [`JsonSchema`] is implemented for a kind `T`, then
948/// [`TypedUuid`]`<T>` will also implement [`JsonSchema`].
949///
950/// If you have a large number of UUID kinds, consider using
951/// [`newtype-uuid-macros`] which comes with several convenience features.
952///
953/// ```
954/// use newtype_uuid_macros::impl_typed_uuid_kinds;
955///
956/// // Invoke this macro with:
957/// impl_typed_uuid_kinds! {
958/// kinds = {
959/// User = {},
960/// Project = {},
961/// // ...
962/// },
963/// }
964/// ```
965///
966/// See [`newtype-uuid-macros`] for more information.
967///
968/// [`newtype-uuid-macros`]: https://docs.rs/newtype-uuid-macros
969/// [`JsonSchema`]: schemars::JsonSchema
970pub trait TypedUuidKind: Send + Sync + 'static {
971 /// Returns the corresponding tag for this kind.
972 ///
973 /// The tag forms a runtime representation of this type.
974 ///
975 /// The tag is required to be a static string.
976 fn tag() -> TypedUuidTag;
977
978 /// Returns a string that corresponds to a type alias for `TypedUuid<Self>`,
979 /// if one is defined.
980 ///
981 /// The type alias must be defined in the same module as `Self`. This
982 /// function is used by the schemars integration to refer to embed a
983 /// reference to that alias in the schema, if available.
984 ///
985 /// This is usually defined by the [`newtype-uuid-macros`] crate.
986 ///
987 /// [`newtype-uuid-macros`]: https://docs.rs/newtype-uuid-macros
988 #[inline]
989 fn alias() -> Option<&'static str> {
990 None
991 }
992}
993
994/// Describes what kind of [`TypedUuid`] something is.
995///
996/// This is the runtime equivalent of [`TypedUuidKind`].
997#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
998pub struct TypedUuidTag(&'static str);
999
1000impl TypedUuidTag {
1001 /// Creates a new `TypedUuidTag` from a static string.
1002 ///
1003 /// The string must be non-empty, and consist of:
1004 /// - ASCII letters
1005 /// - digits (only after the first character)
1006 /// - underscores
1007 /// - hyphens (only after the first character)
1008 ///
1009 /// # Panics
1010 ///
1011 /// Panics if the above conditions aren't met. Use [`Self::try_new`] to handle errors instead.
1012 #[must_use]
1013 pub const fn new(tag: &'static str) -> Self {
1014 match Self::try_new_impl(tag) {
1015 Ok(tag) => tag,
1016 Err(message) => panic!("{}", message),
1017 }
1018 }
1019
1020 /// Attempts to create a new `TypedUuidTag` from a static string.
1021 ///
1022 /// The string must be non-empty, and consist of:
1023 /// - ASCII letters
1024 /// - digits (only after the first character)
1025 /// - underscores
1026 /// - hyphens (only after the first character)
1027 ///
1028 /// # Errors
1029 ///
1030 /// Returns a [`TagError`] if the above conditions aren't met.
1031 pub const fn try_new(tag: &'static str) -> Result<Self, TagError> {
1032 match Self::try_new_impl(tag) {
1033 Ok(tag) => Ok(tag),
1034 Err(message) => Err(TagError {
1035 input: tag,
1036 message,
1037 }),
1038 }
1039 }
1040
1041 const fn try_new_impl(tag: &'static str) -> Result<Self, &'static str> {
1042 if tag.is_empty() {
1043 return Err("tag must not be empty");
1044 }
1045
1046 let bytes = tag.as_bytes();
1047 if !(bytes[0].is_ascii_alphabetic() || bytes[0] == b'_') {
1048 return Err("first character of tag must be an ASCII letter or underscore");
1049 }
1050
1051 let mut bytes = match bytes {
1052 [_, rest @ ..] => rest,
1053 [] => panic!("already checked that it's non-empty"),
1054 };
1055 while let [rest @ .., last] = &bytes {
1056 if !(last.is_ascii_alphanumeric() || *last == b'_' || *last == b'-') {
1057 break;
1058 }
1059 bytes = rest;
1060 }
1061
1062 if !bytes.is_empty() {
1063 return Err("tag must only contain ASCII letters, digits, underscores, or hyphens");
1064 }
1065
1066 Ok(Self(tag))
1067 }
1068
1069 /// Returns the tag as a string.
1070 pub const fn as_str(&self) -> &'static str {
1071 self.0
1072 }
1073}
1074
1075impl fmt::Display for TypedUuidTag {
1076 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1077 f.write_str(self.0)
1078 }
1079}
1080
1081impl AsRef<str> for TypedUuidTag {
1082 fn as_ref(&self) -> &str {
1083 self.0
1084 }
1085}
1086
1087/// An error that occurred while creating a [`TypedUuidTag`].
1088#[derive(Clone, Debug)]
1089#[non_exhaustive]
1090pub struct TagError {
1091 /// The input string.
1092 pub input: &'static str,
1093
1094 /// The error message.
1095 pub message: &'static str,
1096}
1097
1098impl fmt::Display for TagError {
1099 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1100 write!(
1101 f,
1102 "error creating tag from '{}': {}",
1103 self.input, self.message
1104 )
1105 }
1106}
1107
1108#[cfg(feature = "std")]
1109impl std::error::Error for TagError {}
1110
1111/// An error that occurred while parsing a [`TypedUuid`].
1112#[derive(Clone, Debug)]
1113#[non_exhaustive]
1114pub struct ParseError {
1115 /// The underlying error.
1116 pub error: uuid::Error,
1117
1118 /// The tag of the UUID that failed to parse.
1119 pub tag: TypedUuidTag,
1120}
1121
1122impl fmt::Display for ParseError {
1123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1124 write!(f, "error parsing UUID ({})", self.tag)
1125 }
1126}
1127
1128#[cfg(feature = "std")]
1129impl std::error::Error for ParseError {
1130 fn source(&self) -> Option<&(dyn std::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}