Skip to main content

deser_core/ext/
uuid.rs

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/// A UUID.
11///
12/// This is a well-known extension type (see [`ext`](crate::ext)) which
13/// holds the 16 bytes of the UUID.  The fallback is the hyphenated
14/// representation as string (`67e55044-10b1-426f-9247-bb680e5fe0c8`).
15///
16/// When deserializing, the hyphenated and the simple representation (32
17/// hex digits) as well as 16 bytes are accepted.
18///
19/// ```
20/// use deser::ext::Uuid;
21///
22/// let uuid: Uuid = "67e55044-10b1-426f-9247-bb680e5fe0c8".parse().unwrap();
23/// assert_eq!(uuid.0[0], 0x67);
24/// assert_eq!(uuid.to_string(), "67e55044-10b1-426f-9247-bb680e5fe0c8");
25/// ```
26///
27/// With the `uuid` feature, `uuid::Uuid` serializes as [`Uuid`].
28#[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    /// Accepts UUIDs, strings and 16 bytes.
93    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}