use core::fmt;
use core::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum UuidError {
InvalidLength(usize),
InvalidFormat,
InvalidChar(char),
}
impl fmt::Display for UuidError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength(len) => {
write!(f, "UUID must be 16 bytes (got {len})")
}
Self::InvalidFormat => {
write!(
f,
"UUID must be in format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
)
}
Self::InvalidChar(c) => {
write!(f, "UUID contains invalid character '{c}'")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for UuidError {}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "zeroize", derive(Zeroize))]
pub struct Uuid([u8; 16]);
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Uuid {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut bytes = [0x8; 16];
for byte in &mut bytes {
*byte = u8::arbitrary(u)?;
}
Ok(Self(bytes))
}
}
impl Uuid {
#[inline]
#[must_use]
pub const fn new(bytes: [u8; 16]) -> Self {
Self(bytes)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, UuidError> {
if bytes.len() != 16 {
return Err(UuidError::InvalidLength(bytes.len()));
}
let mut arr = [0u8; 16];
arr.copy_from_slice(bytes);
Ok(Self(arr))
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Result<Self, UuidError> {
if s.len() != 36 {
return Err(UuidError::InvalidFormat);
}
let chars: Vec<char> = s.chars().collect();
if chars[8] != '-' || chars[13] != '-' || chars[18] != '-' || chars[23] != '-' {
return Err(UuidError::InvalidFormat);
}
let mut bytes = [0u8; 16];
let mut byte_idx = 0;
let mut hex_idx = 0;
for c in &chars {
if *c == '-' {
continue;
}
if !c.is_ascii_hexdigit() {
return Err(UuidError::InvalidChar(*c));
}
let hex = u8::try_from(c.to_digit(16).unwrap()).unwrap();
if hex_idx % 2 == 0 {
bytes[byte_idx] = hex << 4;
} else {
bytes[byte_idx] |= hex;
byte_idx += 1;
}
hex_idx += 1;
}
Ok(Self(bytes))
}
#[must_use]
#[inline]
pub const fn nil() -> Self {
Self([0u8; 16])
}
#[must_use]
pub fn as_str(&self) -> String {
format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
self.0[0],
self.0[1],
self.0[2],
self.0[3],
self.0[4],
self.0[5],
self.0[6],
self.0[7],
self.0[8],
self.0[9],
self.0[10],
self.0[11],
self.0[12],
self.0[13],
self.0[14],
self.0[15]
)
}
#[must_use]
pub fn as_str_upper(&self) -> String {
format!(
"{:02X}{:02X}{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
self.0[0],
self.0[1],
self.0[2],
self.0[3],
self.0[4],
self.0[5],
self.0[6],
self.0[7],
self.0[8],
self.0[9],
self.0[10],
self.0[11],
self.0[12],
self.0[13],
self.0[14],
self.0[15]
)
}
#[must_use]
#[inline]
pub const fn as_inner(&self) -> &[u8; 16] {
&self.0
}
#[must_use]
#[inline]
pub const fn into_inner(self) -> [u8; 16] {
self.0
}
#[must_use]
#[inline]
pub const fn bytes(&self) -> [u8; 16] {
self.0
}
#[must_use]
#[inline]
pub fn is_nil(&self) -> bool {
self.0.iter().all(|&b| b == 0)
}
#[must_use]
#[inline]
pub fn version(&self) -> Option<u8> {
let version = self.0[6] >> 4;
if (1..=5).contains(&version) {
Some(version)
} else {
None
}
}
#[must_use]
#[inline]
pub const fn variant(&self) -> Option<UuidVariant> {
let variant = self.0[8] >> 6;
match variant {
0b00 => Some(UuidVariant::Ncs),
0b10 => Some(UuidVariant::Rfc4122),
0b110 => Some(UuidVariant::Microsoft),
0b111 => Some(UuidVariant::Future),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UuidVariant {
Ncs,
Rfc4122,
Microsoft,
Future,
}
impl TryFrom<&[u8]> for Uuid {
type Error = UuidError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::from_bytes(bytes)
}
}
impl TryFrom<&str> for Uuid {
type Error = UuidError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::from_str(s)
}
}
impl From<Uuid> for [u8; 16] {
fn from(uuid: Uuid) -> Self {
uuid.0
}
}
impl From<[u8; 16]> for Uuid {
fn from(bytes: [u8; 16]) -> Self {
Self(bytes)
}
}
impl FromStr for Uuid {
type Err = UuidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str(s)
}
}
impl fmt::Display for Uuid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
self.0[0],
self.0[1],
self.0[2],
self.0[3],
self.0[4],
self.0[5],
self.0[6],
self.0[7],
self.0[8],
self.0[9],
self.0[10],
self.0[11],
self.0[12],
self.0[13],
self.0[14],
self.0[15]
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_valid_uuid() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert_eq!(uuid.as_str(), "00112233-4455-6677-8899-aabbccddeeff");
}
#[test]
fn test_from_bytes_valid() {
let bytes = vec![
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
];
assert!(Uuid::from_bytes(&bytes).is_ok());
}
#[test]
fn test_from_bytes_invalid_length() {
let bytes = vec![0x00, 0x11, 0x22];
assert_eq!(Uuid::from_bytes(&bytes), Err(UuidError::InvalidLength(3)));
}
#[test]
fn test_from_str_valid() {
assert!(Uuid::from_str("00112233-4455-6677-8899-aabbccddeeff").is_ok());
assert!(Uuid::from_str("00000000-0000-0000-0000-000000000000").is_ok());
assert!(Uuid::from_str("ffffffff-ffff-ffff-ffff-ffffffffffff").is_ok());
}
#[test]
fn test_from_str_invalid_format() {
assert_eq!(
Uuid::from_str("00112233-4455-6677-8899-aabbccddeef"),
Err(UuidError::InvalidFormat)
);
assert_eq!(
Uuid::from_str("00112233-4455-6677-8899-aabbccddeeff-"),
Err(UuidError::InvalidFormat)
);
}
#[test]
fn test_from_str_invalid_char() {
assert_eq!(
Uuid::from_str("00112233-4455-6677-8899-aabbccddeegg"),
Err(UuidError::InvalidChar('g'))
);
assert_eq!(
Uuid::from_str("00112233-4455-6677-8899-aabbccddeeGG"),
Err(UuidError::InvalidChar('G'))
);
}
#[test]
fn test_nil() {
let uuid = Uuid::nil();
assert!(uuid.is_nil());
assert_eq!(uuid.as_str(), "00000000-0000-0000-0000-000000000000");
}
#[test]
fn test_as_str() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert_eq!(uuid.as_str(), "00112233-4455-6677-8899-aabbccddeeff");
}
#[test]
fn test_as_str_upper() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert_eq!(uuid.as_str_upper(), "00112233-4455-6677-8899-AABBCCDDEEFF");
}
#[test]
fn test_as_inner() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
let bytes = uuid.as_inner();
assert_eq!(
bytes,
&[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
]
);
}
#[test]
fn test_into_inner() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
let bytes = uuid.into_inner();
assert_eq!(
bytes,
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
]
);
}
#[test]
fn test_bytes() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert_eq!(
uuid.bytes(),
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
]
);
}
#[test]
fn test_is_nil() {
assert!(Uuid::nil().is_nil());
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert!(!uuid.is_nil());
}
#[test]
fn test_version() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert_eq!(uuid.version(), None);
}
#[test]
fn test_variant() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert_eq!(uuid.variant(), Some(UuidVariant::Rfc4122));
}
#[test]
fn test_try_from_bytes() {
let bytes = vec![
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
];
let uuid = Uuid::try_from(bytes.as_slice()).unwrap();
assert_eq!(
uuid.bytes(),
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
]
);
}
#[test]
fn test_try_from_str() {
let uuid = Uuid::try_from("00112233-4455-6677-8899-aabbccddeeff").unwrap();
assert_eq!(uuid.as_str(), "00112233-4455-6677-8899-aabbccddeeff");
}
#[test]
fn test_from_uuid_to_array() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
let bytes: [u8; 16] = uuid.into();
assert_eq!(
bytes,
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
]
);
}
#[test]
fn test_from_array_to_uuid() {
let bytes = [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
];
let uuid: Uuid = bytes.into();
assert_eq!(uuid.bytes(), bytes);
}
#[test]
fn test_from_str() {
let uuid: Uuid = "00112233-4455-6677-8899-aabbccddeeff".parse().unwrap();
assert_eq!(uuid.as_str(), "00112233-4455-6677-8899-aabbccddeeff");
}
#[test]
fn test_from_str_invalid() {
assert!(
"00112233-4455-6677-8899-aabbccddeef"
.parse::<Uuid>()
.is_err()
);
assert!(
"00112233-4455-6677-8899-aabbccddeeg!p"
.parse::<Uuid>()
.is_err()
);
}
#[test]
fn test_display() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert_eq!(format!("{uuid}"), "00112233-4455-6677-8899-aabbccddeeff");
}
#[test]
fn test_equality() {
let uuid1 = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
let uuid2 = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
let uuid3 = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xfe,
]);
assert_eq!(uuid1, uuid2);
assert_ne!(uuid1, uuid3);
}
#[test]
fn test_ordering() {
let uuid1 = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xfe,
]);
let uuid2 = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert!(uuid1 < uuid2);
}
#[test]
fn test_clone() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
let uuid2 = uuid.clone();
assert_eq!(uuid, uuid2);
}
#[test]
fn test_error_display() {
assert_eq!(
format!("{}", UuidError::InvalidLength(3)),
"UUID must be 16 bytes (got 3)"
);
assert_eq!(
format!("{}", UuidError::InvalidFormat),
"UUID must be in format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
);
assert_eq!(
format!("{}", UuidError::InvalidChar('g')),
"UUID contains invalid character 'g'"
);
}
#[test]
fn test_hash() {
use core::hash::Hash;
use core::hash::Hasher;
#[derive(Default)]
struct SimpleHasher(u64);
impl Hasher for SimpleHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for byte in bytes {
self.0 = self.0.wrapping_mul(31).wrapping_add(*byte as u64);
}
}
}
let uuid1 = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
let uuid2 = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
let uuid3 = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xfe,
]);
let mut hasher1 = SimpleHasher::default();
let mut hasher2 = SimpleHasher::default();
let mut hasher3 = SimpleHasher::default();
uuid1.hash(&mut hasher1);
uuid2.hash(&mut hasher2);
uuid3.hash(&mut hasher3);
assert_eq!(hasher1.finish(), hasher2.finish());
assert_ne!(hasher1.finish(), hasher3.finish());
}
#[test]
fn test_debug() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
assert_eq!(
format!("{:?}", uuid),
"Uuid([0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255])"
);
}
#[test]
fn test_from_into_inner_roundtrip() {
let uuid = Uuid::new([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]);
let bytes = uuid.into_inner();
let uuid2 = Uuid::new(bytes);
assert_eq!(
uuid2.bytes(),
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff
]
);
}
}