diff --git a/src/external/arbitrary_support.rs b/src/external/arbitrary_support.rs index 40c11f5..53047dc 100644 --- a/src/external/arbitrary_support.rs +++ b/src/external/arbitrary_support.rs @@ -1,4 +1,8 @@ -use crate::{std::convert::TryInto, Builder, Uuid}; +use crate::{ + non_nil::NonNilUuid, + std::convert::{TryFrom, TryInto}, + Builder, Uuid, +}; use arbitrary::{Arbitrary, Unstructured}; @@ -16,6 +20,16 @@ impl Arbitrary<'_> for Uuid { (16, Some(16)) } } +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) + } + + fn size_hint(_: usize) -> (usize, Option) { + (16, Some(16)) + } +} #[cfg(test)] mod tests { @@ -42,4 +56,16 @@ mod tests { assert!(uuid.is_err()); } + + #[test] + fn test_arbitrary_non_nil() { + let mut bytes = Unstructured::new(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + let non_nil_uuid = NonNilUuid::arbitrary(&mut bytes).unwrap(); + let uuid: Uuid = non_nil_uuid.into(); + + assert_eq!(Some(Version::Random), uuid.get_version()); + assert_eq!(Variant::RFC4122, uuid.get_variant()); + assert!(!uuid.is_nil()); + } } diff --git a/src/external/serde_support.rs b/src/external/serde_support.rs index f389271..42c0649 100644 --- a/src/external/serde_support.rs +++ b/src/external/serde_support.rs @@ -10,8 +10,10 @@ // except according to those terms. use crate::{ + convert::TryFrom, error::*, fmt::{Braced, Hyphenated, Simple, Urn}, + non_nil::NonNilUuid, std::fmt, Uuid, }; @@ -30,6 +32,15 @@ impl Serialize for Uuid { } } +impl Serialize for NonNilUuid { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + Uuid::from(*self).serialize(serializer) + } +} + impl Serialize for Hyphenated { fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(self.encode_lower(&mut Uuid::encode_buffer())) @@ -127,6 +138,17 @@ impl<'de> Deserialize<'de> for Uuid { } } +impl<'de> Deserialize<'de> for NonNilUuid { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let uuid = Uuid::deserialize(deserializer)?; + + NonNilUuid::try_from(uuid).map_err(|_| de::Error::custom("Uuid cannot be nil")) + } +} + enum ExpectedFormat { Simple, Braced, @@ -732,4 +754,14 @@ mod serde_tests { "UUID parsing failed: invalid length: expected 16 bytes, found 11", ); } + + #[test] + fn test_serde_non_nil_uuid() { + let uuid_str = "f9168c5e-ceb2-4faa-b6bf-329bf39fa1e4"; + let uuid = Uuid::parse_str(uuid_str).unwrap(); + let non_nil_uuid = NonNilUuid::try_from(uuid).unwrap(); + + serde_test::assert_ser_tokens(&non_nil_uuid.readable(), &[Token::Str(uuid_str)]); + serde_test::assert_de_tokens(&non_nil_uuid.readable(), &[Token::Str(uuid_str)]); + } } diff --git a/src/external/slog_support.rs b/src/external/slog_support.rs index cb06255..7e1c419 100644 --- a/src/external/slog_support.rs +++ b/src/external/slog_support.rs @@ -9,7 +9,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::Uuid; +use crate::{non_nil::NonNilUuid, Uuid}; impl slog::Value for Uuid { fn serialize( @@ -22,6 +22,17 @@ impl slog::Value for Uuid { } } +impl slog::Value for NonNilUuid { + fn serialize( + &self, + record: &slog::Record<'_>, + key: slog::Key, + serializer: &mut dyn slog::Serializer, + ) -> Result<(), slog::Error> { + Uuid::from(*self).serialize(record, key, serializer) + } +} + #[cfg(test)] mod tests { use crate::tests::new; diff --git a/src/lib.rs b/src/lib.rs index 7d9ed42..385cd6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -223,13 +223,14 @@ extern crate std; extern crate core as std; #[cfg(all(uuid_unstable, feature = "zerocopy"))] -use zerocopy::{IntoBytes, FromBytes, Immutable, KnownLayout, Unaligned}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; mod builder; mod error; mod parser; pub mod fmt; +pub mod non_nil; pub mod timestamp; pub use timestamp::{context::NoContext, ClockSequence, Timestamp}; diff --git a/src/non_nil.rs b/src/non_nil.rs new file mode 100644 index 0000000..6642a49 --- /dev/null +++ b/src/non_nil.rs @@ -0,0 +1,93 @@ +//! A wrapper type for nil UUIDs that provides a more memory-efficient +//! `Option` representation. + +use core::convert::TryFrom; +use std::{fmt, num::NonZeroU128}; + +use crate::Uuid; + +/// A UUID that is guaranteed not to be the nil UUID. +/// +/// This is useful for representing optional UUIDs more efficiently, as `Option` +/// takes up the same space as `Uuid`. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct NonNilUuid(NonZeroU128); + +impl fmt::Display for NonNilUuid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", Uuid::from(*self)) + } +} + +impl NonNilUuid { + /// Returns the underlying `Uuid`. + #[inline] + pub const fn get(self) -> Uuid { + Uuid::from_u128(self.0.get()) + } +} + +impl From for Uuid { + /// Converts a [`NonNilUuid`] back into a [`Uuid`]. + /// + /// # Examples + /// ``` + /// use uuid::{non_nil::NonNilUuid, Uuid}; + /// use std::convert::TryFrom; + /// + /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef); + /// let non_nil = NonNilUuid::try_from(uuid).unwrap(); + /// let uuid_again = Uuid::from(non_nil); + /// + /// assert_eq!(uuid, uuid_again); + /// ``` + fn from(non_nil: NonNilUuid) -> Self { + Uuid::from_u128(non_nil.0.get()) + } +} + +impl TryFrom for NonNilUuid { + type Error = &'static str; + + /// Attempts to convert a [`Uuid`] into a [`NonNilUuid`]. + /// + /// # Examples + /// ``` + /// use uuid::{non_nil::NonNilUuid, Uuid}; + /// use std::convert::TryFrom; + /// + /// 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") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_non_nil_with_option_size() { + assert_eq!( + std::mem::size_of::>(), + std::mem::size_of::() + ); + } + + #[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); + + let nil_uuid = Uuid::nil(); + let nn_uuid = NonNilUuid::try_from(nil_uuid); + assert!(nn_uuid.is_err()); + } +}