use core::fmt;
use core::marker::PhantomData;
use core::str::FromStr;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::domain::UuidDomain;
use crate::error::UuidParseError;
pub struct Uuid<D: UuidDomain> {
inner: ::uuid::Uuid,
_marker: PhantomData<D>,
}
impl<D: UuidDomain> fmt::Debug for Uuid<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({})", D::DOMAIN_NAME, self.inner)
}
}
impl<D: UuidDomain> Copy for Uuid<D> {}
impl<D: UuidDomain> Clone for Uuid<D> {
fn clone(&self) -> Self {
*self
}
}
impl<D: UuidDomain> PartialEq for Uuid<D> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<D: UuidDomain> Eq for Uuid<D> {}
impl<D: UuidDomain> PartialOrd for Uuid<D> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<D: UuidDomain> Ord for Uuid<D> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.inner.cmp(&other.inner)
}
}
impl<D: UuidDomain> core::hash::Hash for Uuid<D> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.inner.hash(state);
}
}
impl<D: UuidDomain> Uuid<D> {
pub fn parse(s: &str) -> Result<Self, UuidParseError> {
s.parse()
}
#[inline]
#[must_use]
pub const fn nil() -> Self {
Self {
inner: ::uuid::Uuid::nil(),
_marker: PhantomData,
}
}
#[inline]
#[must_use]
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self {
inner: ::uuid::Uuid::from_bytes(bytes),
_marker: PhantomData,
}
}
#[inline]
#[must_use]
pub fn is_nil(&self) -> bool {
self.inner.is_nil()
}
#[inline]
#[must_use]
pub const fn get(&self) -> ::uuid::Uuid {
self.inner
}
#[inline]
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 16] {
self.inner.as_bytes()
}
#[inline]
#[must_use]
pub fn domain(&self) -> &'static str {
D::DOMAIN_NAME
}
#[cfg(any(feature = "uuid-v4", feature = "uuid-v7"))]
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
inner: Self::generate_inner(),
_marker: PhantomData,
}
}
#[cfg(feature = "uuid-v7")]
#[inline]
fn generate_inner() -> ::uuid::Uuid {
::uuid::Uuid::now_v7()
}
#[cfg(all(feature = "uuid-v4", not(feature = "uuid-v7")))]
#[inline]
fn generate_inner() -> ::uuid::Uuid {
::uuid::Uuid::new_v4()
}
#[cfg(feature = "uuid-v4")]
#[deprecated(
since = "0.3.0",
note = "Use Uuid::<D>::new() instead of Uuid::<D>::v4()"
)]
#[inline]
#[must_use]
pub fn v4() -> Self {
Self {
inner: ::uuid::Uuid::new_v4(),
_marker: PhantomData,
}
}
#[cfg(feature = "uuid-v7")]
#[inline]
#[must_use]
pub fn now_v7() -> Self {
Self {
inner: ::uuid::Uuid::now_v7(),
_marker: PhantomData,
}
}
}
impl<D: UuidDomain> Default for Uuid<D> {
fn default() -> Self {
Self::nil()
}
}
impl<D: UuidDomain> fmt::Display for Uuid<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.inner, f)
}
}
impl<D: UuidDomain> FromStr for Uuid<D> {
type Err = UuidParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let uuid = ::uuid::Uuid::parse_str(s)?;
Ok(Self {
inner: uuid,
_marker: PhantomData,
})
}
}
impl<D: UuidDomain> From<::uuid::Uuid> for Uuid<D> {
#[inline]
fn from(uuid: ::uuid::Uuid) -> Self {
Self {
inner: uuid,
_marker: PhantomData,
}
}
}
impl<D: UuidDomain> From<Uuid<D>> for ::uuid::Uuid {
#[inline]
fn from(typed: Uuid<D>) -> Self {
typed.inner
}
}
impl<D: UuidDomain> From<[u8; 16]> for Uuid<D> {
#[inline]
fn from(bytes: [u8; 16]) -> Self {
Self::from_bytes(bytes)
}
}
impl<D: UuidDomain> TryFrom<&str> for Uuid<D> {
type Error = UuidParseError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
s.parse()
}
}
impl<D: UuidDomain> TryFrom<String> for Uuid<D> {
type Error = UuidParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.parse()
}
}
impl<D: UuidDomain> TryFrom<&[u8]> for Uuid<D> {
type Error = UuidParseError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let inner = ::uuid::Uuid::from_slice(bytes)?;
Ok(Self {
inner,
_marker: PhantomData,
})
}
}
impl<D: UuidDomain> AsRef<::uuid::Uuid> for Uuid<D> {
fn as_ref(&self) -> &::uuid::Uuid {
&self.inner
}
}
#[cfg(feature = "serde")]
impl<D: UuidDomain> Serialize for Uuid<D> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.inner.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, D: UuidDomain> Deserialize<'de> for Uuid<D> {
fn deserialize<De: serde::Deserializer<'de>>(deserializer: De) -> Result<Self, De::Error> {
let inner = ::uuid::Uuid::deserialize(deserializer)?;
Ok(Self {
inner,
_marker: PhantomData,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::{format, string::ToString};
#[derive(Debug)]
struct TestDomain;
impl crate::Domain for TestDomain {
const DOMAIN_NAME: &'static str = "test";
}
impl UuidDomain for TestDomain {}
type TestUuid = Uuid<TestDomain>;
const SAMPLE: &str = "550e8400-e29b-41d4-a716-446655440000";
#[test]
fn test_nil() {
let id = TestUuid::nil();
assert!(id.is_nil());
assert_eq!(id.to_string(), "00000000-0000-0000-0000-000000000000");
}
#[test]
fn test_default_is_nil() {
let id = TestUuid::default();
assert!(id.is_nil());
}
#[test]
fn test_from_uuid() {
let raw = ::uuid::Uuid::nil();
let typed = TestUuid::from(raw);
assert_eq!(typed.get(), raw);
}
#[test]
fn test_parse() {
let id = TestUuid::parse(SAMPLE).unwrap();
assert!(!id.is_nil());
}
#[test]
fn test_parse_invalid() {
assert!(TestUuid::parse("not-a-uuid").is_err());
}
#[test]
fn test_from_bytes() {
let bytes = [1u8; 16];
let id = TestUuid::from_bytes(bytes);
assert_eq!(id.as_bytes(), &bytes);
}
#[test]
fn test_debug_format() {
let id = TestUuid::nil();
assert_eq!(
format!("{id:?}"),
"test(00000000-0000-0000-0000-000000000000)"
);
}
#[test]
fn test_domain() {
let id = TestUuid::nil();
assert_eq!(id.domain(), "test");
}
#[test]
fn test_display() {
let id = TestUuid::nil();
assert_eq!(id.to_string(), "00000000-0000-0000-0000-000000000000");
}
#[test]
fn test_from_str() {
let id: TestUuid = SAMPLE.parse().unwrap();
assert!(!id.is_nil());
}
#[test]
fn test_from_str_invalid() {
let result: Result<TestUuid, _> = "not-a-uuid".parse();
assert!(result.is_err());
}
#[test]
fn test_from_into_uuid() {
let raw = ::uuid::Uuid::nil();
let typed: TestUuid = raw.into();
let back: ::uuid::Uuid = typed.into();
assert_eq!(raw, back);
}
#[test]
fn test_from_byte_array() {
let bytes = [42u8; 16];
let id: TestUuid = bytes.into();
assert_eq!(id.as_bytes(), &bytes);
}
#[test]
fn test_try_from_slice_bytes() {
let bytes = [7u8; 16];
let id = TestUuid::try_from(bytes.as_slice()).unwrap();
assert_eq!(id.as_bytes(), &bytes);
}
#[test]
fn test_try_from_slice_invalid_length() {
let bytes = [0u8; 15];
let result: Result<TestUuid, _> = TestUuid::try_from(bytes.as_slice());
assert!(result.is_err());
}
#[test]
fn test_try_from_str() {
let id = TestUuid::try_from(SAMPLE).unwrap();
assert!(!id.is_nil());
}
#[test]
fn test_try_from_string() {
let id = TestUuid::try_from(String::from(SAMPLE)).unwrap();
assert!(!id.is_nil());
}
#[test]
fn test_copy() {
let id1 = TestUuid::nil();
let id2 = id1; assert_eq!(id1, id2); }
#[test]
fn test_ord() {
let a = TestUuid::nil();
let b: TestUuid = SAMPLE.parse().unwrap();
assert!(a < b);
}
#[test]
fn test_as_ref() {
let id = TestUuid::nil();
let uuid_ref: &::uuid::Uuid = id.as_ref();
assert!(uuid_ref.is_nil());
}
#[cfg(all(feature = "uuid-v4", not(feature = "uuid-v7")))]
#[test]
fn test_new_v4() {
let id = TestUuid::new();
assert!(!id.is_nil());
assert_eq!(id.get().get_version(), Some(::uuid::Version::Random));
}
#[cfg(feature = "uuid-v7")]
#[test]
fn test_new_v7() {
let id = TestUuid::new();
assert!(!id.is_nil());
assert_eq!(id.get().get_version(), Some(::uuid::Version::SortRand));
}
#[cfg(feature = "uuid-v4")]
#[test]
#[allow(deprecated)] fn test_v4_deprecated_alias() {
let id = TestUuid::v4();
assert!(!id.is_nil());
assert_eq!(id.get().get_version(), Some(::uuid::Version::Random));
}
#[cfg(feature = "uuid-v7")]
#[test]
fn test_v7() {
let id = TestUuid::now_v7();
assert!(!id.is_nil());
assert_eq!(id.get().get_version(), Some(::uuid::Version::SortRand));
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_roundtrip() {
let id = TestUuid::parse(SAMPLE).unwrap();
let json = serde_json::to_string(&id).unwrap();
assert!(json.contains("550e8400"));
let deserialized: TestUuid = serde_json::from_str(&json).unwrap();
assert_eq!(id, deserialized);
}
#[test]
fn test_type_safety_different_domains() {
#[derive(Debug)]
struct DomainA;
impl crate::Domain for DomainA {
const DOMAIN_NAME: &'static str = "a";
}
impl UuidDomain for DomainA {}
#[derive(Debug)]
struct DomainB;
impl crate::Domain for DomainB {
const DOMAIN_NAME: &'static str = "b";
}
impl UuidDomain for DomainB {}
let _a: Uuid<DomainA> = Uuid::nil();
let _b: Uuid<DomainB> = Uuid::nil();
}
}