Skip to content

Commit

Permalink
Merge pull request #779 from ab22593k/main
Browse files Browse the repository at this point in the history
feat: Add `NonZeroUuid` type for optimized `Option<Uuid>` representation
  • Loading branch information
KodrAus authored Jan 14, 2025
2 parents 42c2d0f + a4fc89c commit 618e817
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 3 deletions.
28 changes: 27 additions & 1 deletion src/external/arbitrary_support.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -16,6 +20,16 @@ impl Arbitrary<'_> for Uuid {
(16, Some(16))
}
}
impl arbitrary::Arbitrary<'_> for NonNilUuid {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let uuid = Uuid::arbitrary(u)?;
Self::try_from(uuid).map_err(|_| arbitrary::Error::NotEnoughData)
}

fn size_hint(_: usize) -> (usize, Option<usize>) {
(16, Some(16))
}
}

#[cfg(test)]
mod tests {
Expand All @@ -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());
}
}
32 changes: 32 additions & 0 deletions src/external/serde_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -30,6 +32,15 @@ impl Serialize for Uuid {
}
}

impl Serialize for NonNilUuid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
Uuid::from(*self).serialize(serializer)
}
}

impl Serialize for Hyphenated {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.encode_lower(&mut Uuid::encode_buffer()))
Expand Down Expand Up @@ -127,6 +138,17 @@ impl<'de> Deserialize<'de> for Uuid {
}
}

impl<'de> Deserialize<'de> for NonNilUuid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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,
Expand Down Expand Up @@ -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)]);
}
}
13 changes: 12 additions & 1 deletion src/external/slog_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
93 changes: 93 additions & 0 deletions src/non_nil.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! A wrapper type for nil UUIDs that provides a more memory-efficient
//! `Option<NonNilUuid>` 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<NonNilUuid>`
/// 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<NonNilUuid> 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<Uuid> 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<Self, Self::Error> {
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::<Option<NonNilUuid>>(),
std::mem::size_of::<Uuid>()
);
}

#[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());
}
}

0 comments on commit 618e817

Please sign in to comment.