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