mcproto_rs/
uuid.rs

1use crate::utils::*;
2use serde::{Deserializer, Serializer};
3use alloc::{fmt, string::{ToString, String}};
4use fmt::{Display, Debug, Formatter};
5
6#[derive(Copy, Clone, PartialEq, Hash, Eq)]
7pub struct UUID4 {
8    raw: u128,
9}
10
11impl Display for UUID4 {
12    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
13        f.write_str(self.hex().as_str())
14    }
15}
16
17impl Debug for UUID4 {
18    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
19        f.write_str("UUID4{")?;
20        f.write_str(self.hex().as_str())?;
21        f.write_str("}")
22    }
23}
24
25impl From<u128> for UUID4 {
26    fn from(raw: u128) -> Self {
27        UUID4 { raw }
28    }
29}
30
31impl serde::Serialize for UUID4 {
32    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
33    where
34        S: Serializer,
35    {
36        serializer.serialize_str(self.to_string().as_str())
37    }
38}
39
40impl<'de> serde::Deserialize<'de> for UUID4 {
41    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
42    where
43        D: Deserializer<'de>,
44    {
45        struct Visitor;
46        impl serde::de::Visitor<'_> for Visitor {
47            type Value = UUID4;
48
49            fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
50                write!(formatter, "a string representing the UUID")
51            }
52
53            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
54                if let Some(id) = UUID4::parse(v) {
55                    Ok(id)
56                } else {
57                    Err(serde::de::Error::invalid_value(
58                        serde::de::Unexpected::Str(v),
59                        &self,
60                    ))
61                }
62            }
63        }
64
65        deserializer.deserialize_str(Visitor {})
66    }
67}
68
69impl UUID4 {
70    pub fn parse(from: &str) -> Option<UUID4> {
71        RawUUID::from_str(from).and_then(move |raw| raw.parse4())
72    }
73
74    #[cfg(feature = "std")]
75    pub fn random() -> Self {
76        UUID4 {
77            raw: rand::random(),
78        }
79    }
80
81    pub fn to_u128(self) -> u128 {
82        self.raw
83    }
84
85    pub fn hex(self) -> String {
86        let bytes = self.raw.to_be_bytes();
87        let parts = [
88            hex(&bytes[..4]),
89            hex(&bytes[4..6]),
90            hex(&bytes[6..8]),
91            hex(&bytes[8..10]),
92            hex(&bytes[10..16]),
93        ];
94        parts.join("-")
95    }
96}
97
98struct RawUUID<'a> {
99    parts: [&'a str; 5],
100}
101
102impl<'a> RawUUID<'a> {
103    fn from_str(from: &'a str) -> Option<RawUUID<'a>> {
104        const DASH: &'static str = "-";
105        // 8-4-4-4-12
106        // with or without dashes but must be consistent
107        let (s0, mut from) = str_split(from, 8)?;
108        str_check_hex(s0)?;
109        let (from1, has_dash) = str_tag_optional(from, DASH);
110        from = from1;
111        let consume_dash_f = if has_dash {
112            |str: &'a str| str_tag(str, DASH)
113        } else {
114            |str: &'a str| Some(str)
115        };
116
117        let (s1, from) = str_split(from, 4)?;
118        str_check_hex(s1)?;
119        let from = consume_dash_f(from)?;
120        let (s2, from) = str_split(from, 4)?;
121        str_check_hex(s2)?;
122        let from = consume_dash_f(from)?;
123        let (s3, from) = str_split(from, 4)?;
124        str_check_hex(s3)?;
125        let from = consume_dash_f(from)?;
126        let (s4, from) = str_split(from, 12)?;
127        str_check_hex(s4)?;
128        str_check_eof(from)?;
129
130        Some(Self{
131            parts: [s0, s1, s2, s3, s4],
132        })
133    }
134
135    fn parse4(self) -> Option<UUID4> {
136        let mut bit_index: usize = 0;
137        let mut raw: u128 = 0;
138
139        for part in &self.parts {
140            for char in part.chars() {
141                if let Some(parsed) = parse_hex_char(char as u8) {
142                    raw |= (parsed as u128) << (124 - bit_index);
143                    bit_index += 4;
144                } else {
145                    return None;
146                }
147            }
148        }
149
150        Some(UUID4 { raw })
151    }
152}
153
154fn str_tag<'a>(source: &'a str, tag: &str) -> Option<&'a str> {
155    let (front, back) = str_split(source, tag.len())?;
156    if front != tag {
157        None
158    } else {
159        Some(back)
160    }
161}
162
163fn str_tag_optional<'a>(source: &'a str, tag: &str) -> (&'a str, bool) {
164    str_tag(source, tag)
165        .map(move |v| (v, true))
166        .unwrap_or_else(|| (source, false))
167}
168
169fn str_check_eof(source: &str) -> Option<()> {
170    if source.is_empty() {
171        Some(())
172    } else {
173        None
174    }
175}
176
177fn str_check_hex(mut source: &str) -> Option<()> {
178    if source.is_empty() {
179        return None
180    }
181
182    loop {
183        let (part, rest) = str_split(source, 2)?;
184        for c in part.chars() {
185            parse_hex_char(c as u8)?;
186        }
187
188        source = rest;
189        if source.is_empty() {
190            return Some(());
191        }
192    }
193}
194
195fn str_split(source: &str, n: usize) -> Option<(&str, &str)> {
196    if source.len() < n {
197        None
198    } else {
199        Some(source.split_at(n))
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::UUID4;
206    #[cfg(feature = "std")]
207    use alloc::string::ToString;
208
209    #[cfg(feature = "std")]
210    #[test]
211    fn test_random_uuid4() {
212        UUID4::random();
213    }
214
215    const VALID_UUID: &str = "e1cde35a-0758-47f6-adf8-9dcb44884e5d";
216
217    #[test]
218    fn test_uuid4_parse() {
219        UUID4::parse(VALID_UUID).expect("should parse valid uuid correctly");
220    }
221
222    const VALID_UUID_NO_DASHES: &str = "e1cde35a075847f6adf89dcb44884e5d";
223
224    #[test]
225    fn test_uuid4_parse_nodash() {
226        UUID4::parse(VALID_UUID_NO_DASHES).expect("should parse valid uuid with no dashes correctly");
227    }
228
229    #[test]
230    fn test_parsed_uuid4_to_hex() {
231        let uuid_hex = UUID4::parse(VALID_UUID)
232            .expect("should parse valid uuid correctly")
233            .hex();
234
235        assert_eq!(uuid_hex.as_str(), VALID_UUID)
236    }
237
238    #[test]
239    fn test_uuid4_equal() {
240        let uuid_a = UUID4::parse(VALID_UUID).expect("should parse valid uuid correctly");
241        let uuid_b = UUID4::parse(VALID_UUID).expect("should parse valid uuid correctly");
242        assert_eq!(uuid_a, uuid_b);
243    }
244
245    #[cfg(feature = "std")]
246    #[test]
247    fn test_random_uuid4_hex() {
248        let src_uuid = UUID4::random();
249        let uuid_hex = src_uuid.hex();
250        let uuid_parsed =
251            UUID4::parse(uuid_hex.as_str()).expect("should parse generated uuid correctly");
252        assert_eq!(src_uuid, uuid_parsed);
253        let uuid_parsed_hex = uuid_parsed.hex();
254        assert_eq!(uuid_hex, uuid_parsed_hex);
255    }
256
257    #[cfg(feature = "std")]
258    #[test]
259    fn test_display_uuid() {
260        println!("got uuid {}", UUID4::random());
261    }
262
263    #[cfg(feature = "std")]
264    #[test]
265    fn test_debug_uuid() {
266        println!("got uuid {:?}", UUID4::random());
267    }
268
269    #[cfg(feature = "std")]
270    #[test]
271    fn test_to_json() {
272        let id = UUID4::random();
273        let str = serde_json::to_string(&id).expect("should serialize fine");
274        assert_eq!(str, format!("\"{}\"", id.to_string()))
275    }
276
277    #[cfg(feature = "std")]
278    #[test]
279    fn test_from_json() {
280        let id = UUID4::random();
281        let json = format!("\"{}\"", id.to_string());
282        let deserialized: UUID4 = serde_json::from_str(json.as_str()).expect("should read fine");
283        assert_eq!(deserialized, id);
284    }
285
286    #[cfg(all(feature = "std", feature = "bench"))]
287    #[bench]
288    fn bench_parse_uuid4(b: &mut test::Bencher) {
289        let rand = UUID4::random();
290        let str = rand.to_string();
291        b.bytes = str.bytes().len() as u64;
292        b.iter(|| {
293            UUID4::parse(str.as_str()).expect("should parse fine")
294        })
295    }
296
297    #[cfg(all(feature = "std", feature = "bench"))]
298    #[bench]
299    fn bench_uuid4_to_str(b: &mut test::Bencher) {
300        let rand = UUID4::random();
301        b.bytes = 128;
302        b.iter(|| {
303            rand.to_string()
304        })
305    }
306}