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