Skip to content

Commit

Permalink
Merge pull request #783 from uuid-rs/feat/non-nil
Browse files Browse the repository at this point in the history
Finalize `NonNilUuid`
  • Loading branch information
KodrAus authored Jan 14, 2025
2 parents 618e817 + 6c5099e commit 358eb34
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 30 deletions.
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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"),
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/external/arbitrary_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl Arbitrary<'_> for Uuid {
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)
Self::try_from(uuid).map_err(|_| arbitrary::Error::IncorrectFormat)
}

fn size_hint(_: usize) -> (usize, Option<usize>) {
Expand Down
2 changes: 1 addition & 1 deletion src/external/serde_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
}

Expand Down
12 changes: 5 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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)
Expand Down
83 changes: 66 additions & 17 deletions src/non_nil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NonNilUuid>`
/// 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<NonNilUuid>`
/// are the same size as `Uuid`.
#[repr(transparent)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct NonNilUuid(NonZeroU128);

Expand All @@ -19,8 +42,37 @@ impl fmt::Display for NonNilUuid {
}
}

impl PartialEq<Uuid> for NonNilUuid {
fn eq(&self, other: &Uuid) -> bool {
self.get() == *other
}
}

impl PartialEq<NonNilUuid> 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<Self> {
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())
Expand All @@ -32,9 +84,8 @@ impl From<NonNilUuid> 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);
Expand All @@ -47,22 +98,21 @@ impl From<NonNilUuid> for Uuid {
}

impl TryFrom<Uuid> 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<Self, Self::Error> {
NonZeroU128::new(uuid.as_u128())
.map(Self)
.ok_or("Attempted to convert nil Uuid to NonNilUuid")
.ok_or(Error(ErrorKind::Nil))
}
}

Expand All @@ -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());
}
}
12 changes: 9 additions & 3 deletions src/timestamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
(
Expand Down Expand Up @@ -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)
}
Expand All @@ -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()`")
}
Expand Down
2 changes: 1 addition & 1 deletion src/v7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 358eb34

Please sign in to comment.