diff --git a/src/error.rs b/src/error.rs index 30e01750..7a65c2d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,6 +30,8 @@ pub(crate) enum ErrorKind { }, /// The input was not a valid UTF8 string InvalidUTF8, + /// The UUID is nil. + Nil, /// Some other error occurred. Other, } @@ -158,6 +160,7 @@ impl fmt::Display for Error { ) } ErrorKind::InvalidUTF8 => write!(f, "non-UTF8 input"), + ErrorKind::Nil => write!(f, "the UUID is nil"), ErrorKind::Other => write!(f, "failed to parse a UUID"), } } diff --git a/src/external/arbitrary_support.rs b/src/external/arbitrary_support.rs index 53047dce..c753f74c 100644 --- a/src/external/arbitrary_support.rs +++ b/src/external/arbitrary_support.rs @@ -23,7 +23,7 @@ impl Arbitrary<'_> for Uuid { impl arbitrary::Arbitrary<'_> for NonNilUuid { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let uuid = Uuid::arbitrary(u)?; - Self::try_from(uuid).map_err(|_| arbitrary::Error::NotEnoughData) + Self::try_from(uuid).map_err(|_| arbitrary::Error::IncorrectFormat) } fn size_hint(_: usize) -> (usize, Option) { diff --git a/src/external/serde_support.rs b/src/external/serde_support.rs index 42c06493..954a3496 100644 --- a/src/external/serde_support.rs +++ b/src/external/serde_support.rs @@ -145,7 +145,7 @@ impl<'de> Deserialize<'de> for NonNilUuid { { let uuid = Uuid::deserialize(deserializer)?; - NonNilUuid::try_from(uuid).map_err(|_| de::Error::custom("Uuid cannot be nil")) + NonNilUuid::try_from(uuid).map_err(|_| de::Error::invalid_value(de::Unexpected::Other("nil UUID"), &"a non-nil UUID")) } } diff --git a/src/lib.rs b/src/lib.rs index 385cd6c7..edd66430 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -222,15 +222,12 @@ extern crate std; #[macro_use] extern crate core as std; -#[cfg(all(uuid_unstable, feature = "zerocopy"))] -use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; - mod builder; mod error; +mod non_nil; mod parser; pub mod fmt; -pub mod non_nil; pub mod timestamp; pub use timestamp::{context::NoContext, ClockSequence, Timestamp}; @@ -282,7 +279,7 @@ pub mod __macro_support { use crate::std::convert; -pub use crate::{builder::Builder, error::Error}; +pub use crate::{builder::Builder, error::Error, non_nil::NonNilUuid}; /// A 128-bit (16 byte) buffer containing the UUID. /// @@ -437,15 +434,16 @@ pub enum Variant { /// /// The `Uuid` type is always guaranteed to be have the same ABI as [`Bytes`]. #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +// NOTE: Also check `NonNilUuid` when ading new derives here #[cfg_attr( all(uuid_unstable, feature = "zerocopy"), - derive(IntoBytes, FromBytes, KnownLayout, Immutable, Unaligned) + derive(zerocopy::IntoBytes, zerocopy::FromBytes, zerocopy::KnownLayout, zerocopy::Immutable, zerocopy::Unaligned) )] #[cfg_attr( feature = "borsh", derive(borsh_derive::BorshDeserialize, borsh_derive::BorshSerialize) )] -#[repr(transparent)] #[cfg_attr( feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod, bytemuck::TransparentWrapper) diff --git a/src/non_nil.rs b/src/non_nil.rs index 6642a493..ee235a1a 100644 --- a/src/non_nil.rs +++ b/src/non_nil.rs @@ -4,12 +4,35 @@ use core::convert::TryFrom; use std::{fmt, num::NonZeroU128}; -use crate::Uuid; +use crate::{ + error::{Error, ErrorKind}, + Uuid, +}; -/// A UUID that is guaranteed not to be the nil UUID. +/// A UUID that is guaranteed not to be the [nil UUID](https://www.ietf.org/rfc/rfc9562.html#name-nil-uuid). /// /// This is useful for representing optional UUIDs more efficiently, as `Option` /// takes up the same space as `Uuid`. +/// +/// Note that `Uuid`s created by the following methods are guaranteed to be non-nil: +/// +/// - [`Uuid::new_v1`] +/// - [`Uuid::now_v1`] +/// - [`Uuid::new_v3`] +/// - [`Uuid::new_v4`] +/// - [`Uuid::new_v5`] +/// - [`Uuid::new_v6`] +/// - [`Uuid::now_v6`] +/// - [`Uuid::new_v7`] +/// - [`Uuid::now_v7`] +/// - [`Uuid::new_v8`] +/// +/// # ABI +/// +/// The `NonNilUuid` type does not yet have a stable ABI. Its representation or alignment +/// may change. It is currently only guaranteed that `NonNilUuid` and `Option` +/// are the same size as `Uuid`. +#[repr(transparent)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct NonNilUuid(NonZeroU128); @@ -19,8 +42,37 @@ impl fmt::Display for NonNilUuid { } } +impl PartialEq for NonNilUuid { + fn eq(&self, other: &Uuid) -> bool { + self.get() == *other + } +} + +impl PartialEq for Uuid { + fn eq(&self, other: &NonNilUuid) -> bool { + *self == other.get() + } +} + impl NonNilUuid { - /// Returns the underlying `Uuid`. + /// Creates a non-nil UUID if the value is non-nil. + pub const fn new(uuid: Uuid) -> Option { + match NonZeroU128::new(uuid.as_u128()) { + Some(non_nil) => Some(NonNilUuid(non_nil)), + None => None, + } + } + + /// Creates a non-nil without checking whether the value is non-nil. This results in undefined behavior if the value is nil. + /// + /// # Safety + /// + /// The value must not be nil. + pub const unsafe fn new_unchecked(uuid: Uuid) -> Self { + NonNilUuid(unsafe { NonZeroU128::new_unchecked(uuid.as_u128()) }) + } + + /// Get the underlying [`Uuid`] value. #[inline] pub const fn get(self) -> Uuid { Uuid::from_u128(self.0.get()) @@ -32,9 +84,8 @@ impl From for Uuid { /// /// # Examples /// ``` - /// use uuid::{non_nil::NonNilUuid, Uuid}; - /// use std::convert::TryFrom; - /// + /// # use std::convert::TryFrom; + /// # use uuid::{NonNilUuid, Uuid}; /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef); /// let non_nil = NonNilUuid::try_from(uuid).unwrap(); /// let uuid_again = Uuid::from(non_nil); @@ -47,22 +98,21 @@ impl From for Uuid { } impl TryFrom for NonNilUuid { - type Error = &'static str; + type Error = Error; /// Attempts to convert a [`Uuid`] into a [`NonNilUuid`]. /// /// # Examples /// ``` - /// use uuid::{non_nil::NonNilUuid, Uuid}; - /// use std::convert::TryFrom; - /// + /// # use std::convert::TryFrom; + /// # use uuid::{NonNilUuid, Uuid}; /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef); /// let non_nil = NonNilUuid::try_from(uuid).unwrap(); /// ``` fn try_from(uuid: Uuid) -> Result { NonZeroU128::new(uuid.as_u128()) .map(Self) - .ok_or("Attempted to convert nil Uuid to NonNilUuid") + .ok_or(Error(ErrorKind::Nil)) } } @@ -81,13 +131,12 @@ mod tests { #[test] fn test_non_nil() { let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef); - let nn_uuid = NonNilUuid::try_from(uuid); - assert!(nn_uuid.is_ok()); - assert_eq!(Uuid::from(nn_uuid.unwrap()), uuid); + assert_eq!(Uuid::from(NonNilUuid::try_from(uuid).unwrap()), uuid); + assert_eq!(NonNilUuid::new(uuid).unwrap(), uuid); + assert_eq!(unsafe { NonNilUuid::new_unchecked(uuid) }, uuid); - let nil_uuid = Uuid::nil(); - let nn_uuid = NonNilUuid::try_from(nil_uuid); - assert!(nn_uuid.is_err()); + assert!(NonNilUuid::try_from(Uuid::nil()).is_err()); + assert!(NonNilUuid::new(Uuid::nil()).is_none()); } } diff --git a/src/timestamp.rs b/src/timestamp.rs index 56262f1c..3bf9e9b0 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -128,7 +128,7 @@ impl Timestamp { /// /// If conversion from the internal timestamp format to ticks would overflow /// then it will wrap. - /// + /// /// If the internal counter is wider than 14 bits then it will be truncated to 14 bits. pub const fn to_gregorian(&self) -> (u64, u16) { ( @@ -165,7 +165,10 @@ impl Timestamp { #[doc(hidden)] impl Timestamp { - #[deprecated(since = "1.11.1", note = "use `Timestamp::from_gregorian(ticks, counter)`")] + #[deprecated( + since = "1.11.1", + note = "use `Timestamp::from_gregorian(ticks, counter)`" + )] pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { Timestamp::from_gregorian(ticks, counter) } @@ -175,7 +178,10 @@ impl Timestamp { self.to_gregorian() } - #[deprecated(since = "1.2.0", note = "`Timestamp::to_unix_nanos()` is deprecated and will be removed: use `Timestamp::to_unix()`")] + #[deprecated( + since = "1.2.0", + note = "`Timestamp::to_unix_nanos()` is deprecated and will be removed: use `Timestamp::to_unix()`" + )] pub const fn to_unix_nanos(&self) -> u32 { panic!("`Timestamp::to_unix_nanos()` is deprecated and will be removed: use `Timestamp::to_unix()`") } diff --git a/src/v7.rs b/src/v7.rs index c2b4a223..d55cf946 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -31,7 +31,7 @@ impl Uuid { /// # Examples /// /// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit - /// random number. When supplied as such, the data will be combined + /// random number. When supplied as such, the data will be combined /// to ensure uniqueness and sortability at millisecond granularity. /// /// ```rust