1use alloc::string::ToString;
2use core::fmt;
3use core::str::FromStr;
4
5use crate::error::Error;
6use crate::event::Atom;
7use crate::ext::Extension;
8use crate::ext::known::{WellKnown, impl_well_known, invalid};
9
10#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
29pub struct Uuid(pub [u8; 16]);
30
31impl fmt::Display for Uuid {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 for (idx, byte) in self.0.iter().enumerate() {
34 if matches!(idx, 4 | 6 | 8 | 10) {
35 f.write_str("-")?;
36 }
37 write!(f, "{:02x}", byte)?;
38 }
39 Ok(())
40 }
41}
42
43impl fmt::Debug for Uuid {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(f, "Uuid({})", self)
46 }
47}
48
49impl FromStr for Uuid {
50 type Err = Error;
51
52 fn from_str(s: &str) -> Result<Uuid, Error> {
53 let bytes = s.as_bytes();
54 let hyphenated = bytes.len() == 36;
55 if !(hyphenated || bytes.len() == 32) {
56 return Err(invalid("invalid UUID"));
57 }
58 let mut rv = [0u8; 16];
59 let mut pos = 0;
60 for (idx, byte) in rv.iter_mut().enumerate() {
61 if hyphenated && matches!(idx, 4 | 6 | 8 | 10) {
62 if bytes[pos] != b'-' {
63 return Err(invalid("invalid UUID"));
64 }
65 pos += 1;
66 }
67 let hex = |c: u8| {
68 (c as char)
69 .to_digit(16)
70 .ok_or_else(|| invalid("invalid UUID"))
71 };
72 *byte = (hex(bytes[pos])? << 4 | hex(bytes[pos + 1])?) as u8;
73 pos += 2;
74 }
75 Ok(Uuid(rv))
76 }
77}
78
79impl Extension for Uuid {
80 fn name(&self) -> &str {
81 "uuid"
82 }
83
84 fn fallback(&self) -> Atom<'_> {
85 Atom::Str(self.to_string().into())
86 }
87}
88
89impl WellKnown for Uuid {
90 const EXPECTING: &'static str = "uuid";
91
92 fn from_atom(atom: &Atom) -> Result<Option<Uuid>, Error> {
94 Ok(Some(match *atom {
95 Atom::Ext(ref ext) => match ext.downcast_ref::<Uuid>() {
96 Some(value) => *value,
97 None => return Ok(None),
98 },
99 Atom::Str(ref value) => value.parse()?,
100 Atom::Bytes(ref value) => Uuid(
101 value
102 .as_ref()
103 .try_into()
104 .map_err(|_| invalid("invalid UUID, expected 16 bytes"))?,
105 ),
106 _ => return Ok(None),
107 }))
108 }
109}
110
111impl_well_known!(Uuid);
112
113#[test]
114fn test_uuid() {
115 let uuid: Uuid = "67E55044-10b1-426f-9247-bb680e5fe0c8".parse().unwrap();
116 assert_eq!(uuid.to_string(), "67e55044-10b1-426f-9247-bb680e5fe0c8");
117 let simple: Uuid = "67e5504410b1426f9247bb680e5fe0c8".parse().unwrap();
118 assert_eq!(simple, uuid);
119 for invalid in [
120 "",
121 "67e55044-10b1-426f-9247-bb680e5fe0c",
122 "67e55044x10b1-426f-9247-bb680e5fe0c8",
123 "67e55044-10b1-426f-9247-bb680e5fe0cg",
124 "67e5504410b1426f9247bb680e5fe0c8aaaa",
125 ] {
126 assert!(invalid.parse::<Uuid>().is_err());
127 }
128}