use crate::utils::*; use serde::{Deserializer, Serializer}; use alloc::{fmt, string::{ToString, String}}; use fmt::{Display, Debug, Formatter}; #[derive(Copy, Clone, PartialEq, Hash, Eq)] pub struct UUID4 { raw: u128, } impl Display for UUID4 { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(self.hex().as_str()) } } impl Debug for UUID4 { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("UUID4{")?; f.write_str(self.hex().as_str())?; f.write_str("}") } } impl From for UUID4 { fn from(raw: u128) -> Self { UUID4 { raw } } } impl serde::Serialize for UUID4 { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, { serializer.serialize_str(self.to_string().as_str()) } } impl<'de> serde::Deserialize<'de> for UUID4 { fn deserialize(deserializer: D) -> Result>::Error> where D: Deserializer<'de>, { struct Visitor; impl serde::de::Visitor<'_> for Visitor { type Value = UUID4; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { write!(formatter, "a string representing the UUID") } fn visit_str(self, v: &str) -> Result { if let Some(id) = UUID4::parse(v) { Ok(id) } else { Err(serde::de::Error::invalid_value( serde::de::Unexpected::Str(v), &self, )) } } } deserializer.deserialize_str(Visitor {}) } } impl UUID4 { pub fn parse(from: &str) -> Option { RawUUID::from_str(from).and_then(move |raw| raw.parse4()) } #[cfg(feature = "std")] pub fn random() -> Self { UUID4 { raw: rand::random(), } } pub fn to_u128(self) -> u128 { self.raw } pub fn hex(self) -> String { let bytes = self.raw.to_be_bytes(); let parts = [ hex(&bytes[..4]), hex(&bytes[4..6]), hex(&bytes[6..8]), hex(&bytes[8..10]), hex(&bytes[10..16]), ]; parts.join("-") } } struct RawUUID<'a> { parts: [&'a str; 5], } impl<'a> RawUUID<'a> { fn from_str(from: &'a str) -> Option> { const DASH: &'static str = "-"; // 8-4-4-4-12 // with or without dashes but must be consistent let (s0, mut from) = str_split(from, 8)?; str_check_hex(s0)?; let (from1, has_dash) = str_tag_optional(from, DASH); from = from1; let consume_dash_f = if has_dash { |str: &'a str| str_tag(str, DASH) } else { |str: &'a str| Some(str) }; let (s1, from) = str_split(from, 4)?; str_check_hex(s1)?; let from = consume_dash_f(from)?; let (s2, from) = str_split(from, 4)?; str_check_hex(s2)?; let from = consume_dash_f(from)?; let (s3, from) = str_split(from, 4)?; str_check_hex(s3)?; let from = consume_dash_f(from)?; let (s4, from) = str_split(from, 12)?; str_check_hex(s4)?; str_check_eof(from)?; Some(Self{ parts: [s0, s1, s2, s3, s4], }) } fn parse4(self) -> Option { let mut bit_index: usize = 0; let mut raw: u128 = 0; for part in &self.parts { for char in part.chars() { if let Some(parsed) = parse_hex_char(char as u8) { raw |= (parsed as u128) << (124 - bit_index); bit_index += 4; } else { return None; } } } Some(UUID4 { raw }) } } fn str_tag<'a>(source: &'a str, tag: &str) -> Option<&'a str> { let (front, back) = str_split(source, tag.len())?; if front != tag { None } else { Some(back) } } fn str_tag_optional<'a>(source: &'a str, tag: &str) -> (&'a str, bool) { str_tag(source, tag) .map(move |v| (v, true)) .unwrap_or_else(|| (source, false)) } fn str_check_eof(source: &str) -> Option<()> { if source.is_empty() { Some(()) } else { None } } fn str_check_hex(mut source: &str) -> Option<()> { if source.is_empty() { return None } loop { let (part, rest) = str_split(source, 2)?; for c in part.chars() { parse_hex_char(c as u8)?; } source = rest; if source.is_empty() { return Some(()); } } } fn str_split(source: &str, n: usize) -> Option<(&str, &str)> { if source.len() < n { None } else { Some(source.split_at(n)) } } #[cfg(test)] mod tests { use super::UUID4; #[cfg(feature = "std")] use alloc::string::ToString; #[cfg(feature = "std")] #[test] fn test_random_uuid4() { UUID4::random(); } const VALID_UUID: &str = "e1cde35a-0758-47f6-adf8-9dcb44884e5d"; #[test] fn test_uuid4_parse() { UUID4::parse(VALID_UUID).expect("should parse valid uuid correctly"); } const VALID_UUID_NO_DASHES: &str = "e1cde35a075847f6adf89dcb44884e5d"; #[test] fn test_uuid4_parse_nodash() { UUID4::parse(VALID_UUID_NO_DASHES).expect("should parse valid uuid with no dashes correctly"); } #[test] fn test_parsed_uuid4_to_hex() { let uuid_hex = UUID4::parse(VALID_UUID) .expect("should parse valid uuid correctly") .hex(); assert_eq!(uuid_hex.as_str(), VALID_UUID) } #[test] fn test_uuid4_equal() { let uuid_a = UUID4::parse(VALID_UUID).expect("should parse valid uuid correctly"); let uuid_b = UUID4::parse(VALID_UUID).expect("should parse valid uuid correctly"); assert_eq!(uuid_a, uuid_b); } #[cfg(feature = "std")] #[test] fn test_random_uuid4_hex() { let src_uuid = UUID4::random(); let uuid_hex = src_uuid.hex(); let uuid_parsed = UUID4::parse(uuid_hex.as_str()).expect("should parse generated uuid correctly"); assert_eq!(src_uuid, uuid_parsed); let uuid_parsed_hex = uuid_parsed.hex(); assert_eq!(uuid_hex, uuid_parsed_hex); } #[cfg(feature = "std")] #[test] fn test_display_uuid() { println!("got uuid {}", UUID4::random()); } #[cfg(feature = "std")] #[test] fn test_debug_uuid() { println!("got uuid {:?}", UUID4::random()); } #[cfg(feature = "std")] #[test] fn test_to_json() { let id = UUID4::random(); let str = serde_json::to_string(&id).expect("should serialize fine"); assert_eq!(str, format!("\"{}\"", id.to_string())) } #[cfg(feature = "std")] #[test] fn test_from_json() { let id = UUID4::random(); let json = format!("\"{}\"", id.to_string()); let deserialized: UUID4 = serde_json::from_str(json.as_str()).expect("should read fine"); assert_eq!(deserialized, id); } #[cfg(all(feature = "std", feature = "bench"))] #[bench] fn bench_parse_uuid4(b: &mut test::Bencher) { let rand = UUID4::random(); let str = rand.to_string(); b.bytes = str.bytes().len() as u64; b.iter(|| { UUID4::parse(str.as_str()).expect("should parse fine") }) } #[cfg(all(feature = "std", feature = "bench"))] #[bench] fn bench_uuid4_to_str(b: &mut test::Bencher) { let rand = UUID4::random(); b.bytes = 128; b.iter(|| { rand.to_string() }) } }