1use core::fmt;
40use core::str::FromStr;
41
42#[cfg(feature = "serde")]
43use serde::{Deserialize, Serialize};
44
45#[cfg(feature = "zeroize")]
46use zeroize::Zeroize;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51#[non_exhaustive]
52pub enum UuidError {
53 InvalidLength(usize),
58 InvalidFormat,
63 InvalidChar(char),
68}
69
70impl fmt::Display for UuidError {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 match self {
73 Self::InvalidLength(len) => {
74 write!(f, "UUID must be 16 bytes (got {len})")
75 }
76 Self::InvalidFormat => {
77 write!(
78 f,
79 "UUID must be in format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
80 )
81 }
82 Self::InvalidChar(c) => {
83 write!(f, "UUID contains invalid character '{c}'")
84 }
85 }
86 }
87}
88
89#[cfg(feature = "std")]
90impl std::error::Error for UuidError {}
91
92#[repr(transparent)]
125#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
126#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
127#[cfg_attr(feature = "zeroize", derive(Zeroize))]
128pub struct Uuid([u8; 16]);
129
130#[cfg(feature = "arbitrary")]
131impl<'a> arbitrary::Arbitrary<'a> for Uuid {
132 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
133 let mut bytes = [0x8; 16];
134 for byte in &mut bytes {
135 *byte = u8::arbitrary(u)?;
136 }
137 Ok(Self(bytes))
138 }
139}
140
141impl Uuid {
142 #[inline]
154 #[must_use]
155 pub const fn new(bytes: [u8; 16]) -> Self {
156 Self(bytes)
157 }
158
159 pub fn from_bytes(bytes: &[u8]) -> Result<Self, UuidError> {
177 if bytes.len() != 16 {
178 return Err(UuidError::InvalidLength(bytes.len()));
179 }
180 let mut arr = [0u8; 16];
181 arr.copy_from_slice(bytes);
182 Ok(Self(arr))
183 }
184
185 #[allow(clippy::should_implement_trait)]
205 pub fn from_str(s: &str) -> Result<Self, UuidError> {
206 if s.len() != 36 {
207 return Err(UuidError::InvalidFormat);
208 }
209
210 let chars: Vec<char> = s.chars().collect();
211
212 if chars[8] != '-' || chars[13] != '-' || chars[18] != '-' || chars[23] != '-' {
213 return Err(UuidError::InvalidFormat);
214 }
215
216 let mut bytes = [0u8; 16];
217 let mut byte_idx = 0;
218 let mut hex_idx = 0;
219
220 for c in &chars {
221 if *c == '-' {
222 continue;
223 }
224
225 if !c.is_ascii_hexdigit() {
226 return Err(UuidError::InvalidChar(*c));
227 }
228
229 let hex = u8::try_from(c.to_digit(16).unwrap()).unwrap();
230
231 if hex_idx % 2 == 0 {
232 bytes[byte_idx] = hex << 4;
233 } else {
234 bytes[byte_idx] |= hex;
235 byte_idx += 1;
236 }
237 hex_idx += 1;
238 }
239
240 Ok(Self(bytes))
241 }
242
243 #[must_use]
257 #[inline]
258 pub const fn nil() -> Self {
259 Self([0u8; 16])
260 }
261
262 #[must_use]
274 pub fn as_str(&self) -> String {
275 format!(
276 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
277 self.0[0],
278 self.0[1],
279 self.0[2],
280 self.0[3],
281 self.0[4],
282 self.0[5],
283 self.0[6],
284 self.0[7],
285 self.0[8],
286 self.0[9],
287 self.0[10],
288 self.0[11],
289 self.0[12],
290 self.0[13],
291 self.0[14],
292 self.0[15]
293 )
294 }
295
296 #[must_use]
308 pub fn as_str_upper(&self) -> String {
309 format!(
310 "{:02X}{:02X}{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
311 self.0[0],
312 self.0[1],
313 self.0[2],
314 self.0[3],
315 self.0[4],
316 self.0[5],
317 self.0[6],
318 self.0[7],
319 self.0[8],
320 self.0[9],
321 self.0[10],
322 self.0[11],
323 self.0[12],
324 self.0[13],
325 self.0[14],
326 self.0[15]
327 )
328 }
329
330 #[must_use]
344 #[inline]
345 pub const fn as_inner(&self) -> &[u8; 16] {
346 &self.0
347 }
348
349 #[must_use]
363 #[inline]
364 pub const fn into_inner(self) -> [u8; 16] {
365 self.0
366 }
367
368 #[must_use]
381 #[inline]
382 pub const fn bytes(&self) -> [u8; 16] {
383 self.0
384 }
385
386 #[must_use]
398 #[inline]
399 pub fn is_nil(&self) -> bool {
400 self.0.iter().all(|&b| b == 0)
401 }
402
403 #[must_use]
417 #[inline]
418 pub fn version(&self) -> Option<u8> {
419 let version = self.0[6] >> 4;
420 if (1..=5).contains(&version) {
421 Some(version)
422 } else {
423 None
424 }
425 }
426
427 #[must_use]
441 #[inline]
442 pub const fn variant(&self) -> Option<UuidVariant> {
443 let variant = self.0[8] >> 6;
444 match variant {
445 0b00 => Some(UuidVariant::Ncs),
446 0b10 => Some(UuidVariant::Rfc4122),
447 0b110 => Some(UuidVariant::Microsoft),
448 0b111 => Some(UuidVariant::Future),
449 _ => None,
450 }
451 }
452}
453
454#[derive(Debug, Clone, Copy, PartialEq, Eq)]
456#[non_exhaustive]
457pub enum UuidVariant {
458 Ncs,
460 Rfc4122,
462 Microsoft,
464 Future,
466}
467
468impl TryFrom<&[u8]> for Uuid {
469 type Error = UuidError;
470
471 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
472 Self::from_bytes(bytes)
473 }
474}
475
476impl TryFrom<&str> for Uuid {
477 type Error = UuidError;
478
479 fn try_from(s: &str) -> Result<Self, Self::Error> {
480 Self::from_str(s)
481 }
482}
483
484impl From<Uuid> for [u8; 16] {
485 fn from(uuid: Uuid) -> Self {
486 uuid.0
487 }
488}
489
490impl From<[u8; 16]> for Uuid {
491 fn from(bytes: [u8; 16]) -> Self {
492 Self(bytes)
493 }
494}
495
496impl FromStr for Uuid {
497 type Err = UuidError;
498
499 fn from_str(s: &str) -> Result<Self, Self::Err> {
500 Self::from_str(s)
501 }
502}
503
504impl fmt::Display for Uuid {
505 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506 write!(
507 f,
508 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
509 self.0[0],
510 self.0[1],
511 self.0[2],
512 self.0[3],
513 self.0[4],
514 self.0[5],
515 self.0[6],
516 self.0[7],
517 self.0[8],
518 self.0[9],
519 self.0[10],
520 self.0[11],
521 self.0[12],
522 self.0[13],
523 self.0[14],
524 self.0[15]
525 )
526 }
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532
533 #[test]
534 fn test_new_valid_uuid() {
535 let uuid = Uuid::new([
536 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
537 0xee, 0xff,
538 ]);
539 assert_eq!(uuid.as_str(), "00112233-4455-6677-8899-aabbccddeeff");
540 }
541
542 #[test]
543 fn test_from_bytes_valid() {
544 let bytes = vec![
545 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
546 0xee, 0xff,
547 ];
548 assert!(Uuid::from_bytes(&bytes).is_ok());
549 }
550
551 #[test]
552 fn test_from_bytes_invalid_length() {
553 let bytes = vec![0x00, 0x11, 0x22];
554 assert_eq!(Uuid::from_bytes(&bytes), Err(UuidError::InvalidLength(3)));
555 }
556
557 #[test]
558 fn test_from_str_valid() {
559 assert!(Uuid::from_str("00112233-4455-6677-8899-aabbccddeeff").is_ok());
560 assert!(Uuid::from_str("00000000-0000-0000-0000-000000000000").is_ok());
561 assert!(Uuid::from_str("ffffffff-ffff-ffff-ffff-ffffffffffff").is_ok());
562 }
563
564 #[test]
565 fn test_from_str_invalid_format() {
566 assert_eq!(
567 Uuid::from_str("00112233-4455-6677-8899-aabbccddeef"),
568 Err(UuidError::InvalidFormat)
569 );
570 assert_eq!(
571 Uuid::from_str("00112233-4455-6677-8899-aabbccddeeff-"),
572 Err(UuidError::InvalidFormat)
573 );
574 }
575
576 #[test]
577 fn test_from_str_invalid_char() {
578 assert_eq!(
579 Uuid::from_str("00112233-4455-6677-8899-aabbccddeegg"),
580 Err(UuidError::InvalidChar('g'))
581 );
582 assert_eq!(
583 Uuid::from_str("00112233-4455-6677-8899-aabbccddeeGG"),
584 Err(UuidError::InvalidChar('G'))
585 );
586 }
587
588 #[test]
589 fn test_nil() {
590 let uuid = Uuid::nil();
591 assert!(uuid.is_nil());
592 assert_eq!(uuid.as_str(), "00000000-0000-0000-0000-000000000000");
593 }
594
595 #[test]
596 fn test_as_str() {
597 let uuid = Uuid::new([
598 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
599 0xee, 0xff,
600 ]);
601 assert_eq!(uuid.as_str(), "00112233-4455-6677-8899-aabbccddeeff");
602 }
603
604 #[test]
605 fn test_as_str_upper() {
606 let uuid = Uuid::new([
607 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
608 0xee, 0xff,
609 ]);
610 assert_eq!(uuid.as_str_upper(), "00112233-4455-6677-8899-AABBCCDDEEFF");
611 }
612
613 #[test]
614 fn test_as_inner() {
615 let uuid = Uuid::new([
616 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
617 0xee, 0xff,
618 ]);
619 let bytes = uuid.as_inner();
620 assert_eq!(
621 bytes,
622 &[
623 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
624 0xee, 0xff
625 ]
626 );
627 }
628
629 #[test]
630 fn test_into_inner() {
631 let uuid = Uuid::new([
632 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
633 0xee, 0xff,
634 ]);
635 let bytes = uuid.into_inner();
636 assert_eq!(
637 bytes,
638 [
639 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
640 0xee, 0xff
641 ]
642 );
643 }
644
645 #[test]
646 fn test_bytes() {
647 let uuid = Uuid::new([
648 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
649 0xee, 0xff,
650 ]);
651 assert_eq!(
652 uuid.bytes(),
653 [
654 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
655 0xee, 0xff
656 ]
657 );
658 }
659
660 #[test]
661 fn test_is_nil() {
662 assert!(Uuid::nil().is_nil());
663 let uuid = Uuid::new([
664 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
665 0xee, 0xff,
666 ]);
667 assert!(!uuid.is_nil());
668 }
669
670 #[test]
671 fn test_version() {
672 let uuid = Uuid::new([
673 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
674 0xee, 0xff,
675 ]);
676 assert_eq!(uuid.version(), None);
677 }
678
679 #[test]
680 fn test_variant() {
681 let uuid = Uuid::new([
682 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
683 0xee, 0xff,
684 ]);
685 assert_eq!(uuid.variant(), Some(UuidVariant::Rfc4122));
686 }
687
688 #[test]
689 fn test_try_from_bytes() {
690 let bytes = vec![
691 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
692 0xee, 0xff,
693 ];
694 let uuid = Uuid::try_from(bytes.as_slice()).unwrap();
695 assert_eq!(
696 uuid.bytes(),
697 [
698 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
699 0xee, 0xff
700 ]
701 );
702 }
703
704 #[test]
705 fn test_try_from_str() {
706 let uuid = Uuid::try_from("00112233-4455-6677-8899-aabbccddeeff").unwrap();
707 assert_eq!(uuid.as_str(), "00112233-4455-6677-8899-aabbccddeeff");
708 }
709
710 #[test]
711 fn test_from_uuid_to_array() {
712 let uuid = Uuid::new([
713 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
714 0xee, 0xff,
715 ]);
716 let bytes: [u8; 16] = uuid.into();
717 assert_eq!(
718 bytes,
719 [
720 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
721 0xee, 0xff
722 ]
723 );
724 }
725
726 #[test]
727 fn test_from_array_to_uuid() {
728 let bytes = [
729 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
730 0xee, 0xff,
731 ];
732 let uuid: Uuid = bytes.into();
733 assert_eq!(uuid.bytes(), bytes);
734 }
735
736 #[test]
737 fn test_from_str() {
738 let uuid: Uuid = "00112233-4455-6677-8899-aabbccddeeff".parse().unwrap();
739 assert_eq!(uuid.as_str(), "00112233-4455-6677-8899-aabbccddeeff");
740 }
741
742 #[test]
743 fn test_from_str_invalid() {
744 assert!(
745 "00112233-4455-6677-8899-aabbccddeef"
746 .parse::<Uuid>()
747 .is_err()
748 );
749 assert!(
750 "00112233-4455-6677-8899-aabbccddeeg!p"
751 .parse::<Uuid>()
752 .is_err()
753 );
754 }
755
756 #[test]
757 fn test_display() {
758 let uuid = Uuid::new([
759 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
760 0xee, 0xff,
761 ]);
762 assert_eq!(format!("{uuid}"), "00112233-4455-6677-8899-aabbccddeeff");
763 }
764
765 #[test]
766 fn test_equality() {
767 let uuid1 = Uuid::new([
768 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
769 0xee, 0xff,
770 ]);
771 let uuid2 = Uuid::new([
772 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
773 0xee, 0xff,
774 ]);
775 let uuid3 = Uuid::new([
776 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
777 0xee, 0xfe,
778 ]);
779
780 assert_eq!(uuid1, uuid2);
781 assert_ne!(uuid1, uuid3);
782 }
783
784 #[test]
785 fn test_ordering() {
786 let uuid1 = Uuid::new([
787 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
788 0xee, 0xfe,
789 ]);
790 let uuid2 = Uuid::new([
791 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
792 0xee, 0xff,
793 ]);
794
795 assert!(uuid1 < uuid2);
796 }
797
798 #[test]
799 fn test_clone() {
800 let uuid = Uuid::new([
801 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
802 0xee, 0xff,
803 ]);
804 let uuid2 = uuid.clone();
805 assert_eq!(uuid, uuid2);
806 }
807
808 #[test]
809 fn test_error_display() {
810 assert_eq!(
811 format!("{}", UuidError::InvalidLength(3)),
812 "UUID must be 16 bytes (got 3)"
813 );
814 assert_eq!(
815 format!("{}", UuidError::InvalidFormat),
816 "UUID must be in format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
817 );
818 assert_eq!(
819 format!("{}", UuidError::InvalidChar('g')),
820 "UUID contains invalid character 'g'"
821 );
822 }
823
824 #[test]
825 fn test_hash() {
826 use core::hash::Hash;
827 use core::hash::Hasher;
828
829 #[derive(Default)]
830 struct SimpleHasher(u64);
831
832 impl Hasher for SimpleHasher {
833 fn finish(&self) -> u64 {
834 self.0
835 }
836
837 fn write(&mut self, bytes: &[u8]) {
838 for byte in bytes {
839 self.0 = self.0.wrapping_mul(31).wrapping_add(*byte as u64);
840 }
841 }
842 }
843
844 let uuid1 = Uuid::new([
845 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
846 0xee, 0xff,
847 ]);
848 let uuid2 = Uuid::new([
849 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
850 0xee, 0xff,
851 ]);
852 let uuid3 = Uuid::new([
853 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
854 0xee, 0xfe,
855 ]);
856
857 let mut hasher1 = SimpleHasher::default();
858 let mut hasher2 = SimpleHasher::default();
859 let mut hasher3 = SimpleHasher::default();
860
861 uuid1.hash(&mut hasher1);
862 uuid2.hash(&mut hasher2);
863 uuid3.hash(&mut hasher3);
864
865 assert_eq!(hasher1.finish(), hasher2.finish());
866 assert_ne!(hasher1.finish(), hasher3.finish());
867 }
868
869 #[test]
870 fn test_debug() {
871 let uuid = Uuid::new([
872 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
873 0xee, 0xff,
874 ]);
875 assert_eq!(
876 format!("{:?}", uuid),
877 "Uuid([0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255])"
878 );
879 }
880
881 #[test]
882 fn test_from_into_inner_roundtrip() {
883 let uuid = Uuid::new([
884 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
885 0xee, 0xff,
886 ]);
887 let bytes = uuid.into_inner();
888 let uuid2 = Uuid::new(bytes);
889 assert_eq!(
890 uuid2.bytes(),
891 [
892 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
893 0xee, 0xff
894 ]
895 );
896 }
897}