diff --git a/shared/error.rs b/shared/error.rs index 9c0bce74..73510726 100644 --- a/shared/error.rs +++ b/shared/error.rs @@ -14,6 +14,9 @@ pub(crate) enum ErrorKind { /// /// [`Uuid`]: ../struct.Uuid.html SimpleLength { len: usize }, + /// A byte array didn't contain 16 bytes + #[allow(dead_code)] + ByteLength { len: usize }, /// A hyphenated [`Uuid`] didn't contain 5 groups /// /// [`Uuid`]: ../struct.Uuid.html @@ -26,6 +29,9 @@ pub(crate) enum ErrorKind { len: usize, index: usize, }, + /// Some other error occurred. + #[allow(dead_code)] + Other, } /// A string that is guaranteed to fail to parse to a [`Uuid`]. @@ -128,6 +134,9 @@ impl fmt::Display for Error { len ) } + ErrorKind::ByteLength { len } => { + write!(f, "invalid length: expected 16 bytes, found {}", len) + } ErrorKind::GroupCount { count } => { write!(f, "invalid group count: expected 5, found {}", count) } @@ -139,6 +148,7 @@ impl fmt::Display for Error { group, expected, len ) } + ErrorKind::Other => write!(f, "failed to parse a UUID"), } } } diff --git a/src/builder.rs b/src/builder.rs index 364eefb7..e656eada 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -308,7 +308,7 @@ impl Uuid { /// ``` pub fn from_slice(b: &[u8]) -> Result { if b.len() != 16 { - return Err(Error(ErrorKind::SimpleLength { len: b.len() * 2 })); + return Err(Error(ErrorKind::ByteLength { len: b.len() })); } let mut bytes: Bytes = [0; 16]; @@ -349,7 +349,7 @@ impl Uuid { /// ``` pub fn from_slice_le(b: &[u8]) -> Result { if b.len() != 16 { - return Err(Error(ErrorKind::SimpleLength { len: b.len() * 2 })); + return Err(Error(ErrorKind::ByteLength { len: b.len() })); } let mut bytes: Bytes = [0; 16]; @@ -452,9 +452,7 @@ impl Uuid { /// ``` pub fn from_bytes_ref(bytes: &Bytes) -> &Uuid { // SAFETY: `Bytes` and `Uuid` have the same ABI - unsafe { - &*(bytes as *const Bytes as *const Uuid) - } + unsafe { &*(bytes as *const Bytes as *const Uuid) } } } diff --git a/src/external/mod.rs b/src/external/mod.rs index 8de6635b..219a9236 100644 --- a/src/external/mod.rs +++ b/src/external/mod.rs @@ -1,6 +1,6 @@ #[cfg(feature = "arbitrary")] -mod arbitrary_support; +pub(crate) mod arbitrary_support; #[cfg(feature = "serde")] -mod serde_support; +pub(crate) mod serde_support; #[cfg(feature = "slog")] -mod slog_support; +pub(crate) mod slog_support; diff --git a/src/external/serde_support.rs b/src/external/serde_support.rs index 8ec70ca3..8a139b87 100644 --- a/src/external/serde_support.rs +++ b/src/external/serde_support.rs @@ -11,9 +11,7 @@ use crate::{ error::*, - fmt::{ - Braced, Hyphenated, Simple, Urn, - }, + fmt::{Braced, Hyphenated, Simple, Urn}, std::fmt, Uuid, }; @@ -29,11 +27,10 @@ impl Serialize for Uuid { ) -> Result { if serializer.is_human_readable() { serializer.serialize_str( - self.hyphenated() - .encode_lower(&mut Uuid::encode_buffer()), + self.hyphenated().encode_lower(&mut Uuid::encode_buffer()), ) } else { - self.as_bytes().serialize(serializer) + serializer.serialize_bytes(self.as_bytes()) } } } @@ -139,9 +136,110 @@ impl<'de> Deserialize<'de> for Uuid { deserializer.deserialize_str(UuidVisitor) } else { - let bytes: [u8; 16] = Deserialize::deserialize(deserializer)?; + struct UuidBytesVisitor; - Ok(Uuid::from_bytes(bytes)) + impl<'vi> de::Visitor<'vi> for UuidBytesVisitor { + type Value = Uuid; + + fn expecting( + &self, + formatter: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + write!(formatter, "bytes") + } + + fn visit_bytes( + self, + value: &[u8], + ) -> Result { + Uuid::from_slice(value).map_err(de_error) + } + } + + deserializer.deserialize_bytes(UuidBytesVisitor) + } + } +} + +pub mod compact { + //! Serialize a [`Uuid`] as a `[u8; 16]`. + //! + //! [`Uuid`]: ../../struct.Uuid.html + + /// Serialize from a [`Uuid`] as a `[u8; 16]` + /// + /// [`Uuid`]: ../../struct.Uuid.html + pub fn serialize( + u: &crate::Uuid, + serializer: S, + ) -> Result + where + S: serde::Serializer, + { + serde::Serialize::serialize(u.as_bytes(), serializer) + } + + /// Deserialize a `[u8; 16]` as a [`Uuid`] + /// + /// [`Uuid`]: ../../struct.Uuid.html + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes: [u8; 16] = serde::Deserialize::deserialize(deserializer)?; + + Ok(crate::Uuid::from_bytes(bytes)) + } + + #[cfg(test)] + mod tests { + use serde_derive::*; + use serde_test::{self, Configure}; + + #[test] + fn test_serialize_compact() { + #[derive(Serialize, Debug, Deserialize, PartialEq)] + struct UuidContainer { + #[serde(with = "crate::serde::compact")] + u: crate::Uuid, + } + + let uuid_bytes = b"F9168C5E-CEB2-4F"; + let container = UuidContainer { + u: crate::Uuid::from_slice(uuid_bytes).unwrap(), + }; + + // more complex because of the struct wrapping the actual UUID + // serialization + serde_test::assert_tokens( + &container.compact(), + &[ + serde_test::Token::Struct { + name: "UuidContainer", + len: 1, + }, + serde_test::Token::Str("u"), + serde_test::Token::Tuple { len: 16 }, + serde_test::Token::U8(uuid_bytes[0]), + serde_test::Token::U8(uuid_bytes[1]), + serde_test::Token::U8(uuid_bytes[2]), + serde_test::Token::U8(uuid_bytes[3]), + serde_test::Token::U8(uuid_bytes[4]), + serde_test::Token::U8(uuid_bytes[5]), + serde_test::Token::U8(uuid_bytes[6]), + serde_test::Token::U8(uuid_bytes[7]), + serde_test::Token::U8(uuid_bytes[8]), + serde_test::Token::U8(uuid_bytes[9]), + serde_test::Token::U8(uuid_bytes[10]), + serde_test::Token::U8(uuid_bytes[11]), + serde_test::Token::U8(uuid_bytes[12]), + serde_test::Token::U8(uuid_bytes[13]), + serde_test::Token::U8(uuid_bytes[14]), + serde_test::Token::U8(uuid_bytes[15]), + serde_test::Token::TupleEnd, + serde_test::Token::StructEnd, + ], + ) } } } @@ -204,10 +302,7 @@ mod serde_tests { fn test_serialize_hyphenated() { let uuid_str = "f9168c5e-ceb2-4faa-b6bf-329bf39fa1e4"; let u = Uuid::parse_str(uuid_str).unwrap(); - serde_test::assert_ser_tokens( - &u.hyphenated(), - &[Token::Str(uuid_str)], - ); + serde_test::assert_ser_tokens(&u.hyphenated(), &[Token::Str(uuid_str)]); } #[test] @@ -232,31 +327,14 @@ mod serde_tests { } #[test] - fn test_serialize_compact() { + fn test_serialize_non_human_readable() { let uuid_bytes = b"F9168C5E-CEB2-4F"; let u = Uuid::from_slice(uuid_bytes).unwrap(); serde_test::assert_tokens( &u.compact(), - &[ - serde_test::Token::Tuple { len: 16 }, - serde_test::Token::U8(uuid_bytes[0]), - serde_test::Token::U8(uuid_bytes[1]), - serde_test::Token::U8(uuid_bytes[2]), - serde_test::Token::U8(uuid_bytes[3]), - serde_test::Token::U8(uuid_bytes[4]), - serde_test::Token::U8(uuid_bytes[5]), - serde_test::Token::U8(uuid_bytes[6]), - serde_test::Token::U8(uuid_bytes[7]), - serde_test::Token::U8(uuid_bytes[8]), - serde_test::Token::U8(uuid_bytes[9]), - serde_test::Token::U8(uuid_bytes[10]), - serde_test::Token::U8(uuid_bytes[11]), - serde_test::Token::U8(uuid_bytes[12]), - serde_test::Token::U8(uuid_bytes[13]), - serde_test::Token::U8(uuid_bytes[14]), - serde_test::Token::U8(uuid_bytes[15]), - serde_test::Token::TupleEnd, - ], + &[serde_test::Token::Bytes(&[ + 70, 57, 49, 54, 56, 67, 53, 69, 45, 67, 69, 66, 50, 45, 52, 70, + ])], ); } @@ -269,7 +347,7 @@ mod serde_tests { serde_test::assert_de_tokens_error::>( &[Token::Bytes(b"hello_world")], - "invalid type: byte array, expected an array of length 16", + "UUID parsing failed: invalid length: expected 16 bytes, found 11", ); } } diff --git a/src/lib.rs b/src/lib.rs index aa7c5bc0..c314387b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -293,8 +293,7 @@ pub enum Variant { /// * [`hyphenated`](#method.hyphenated): /// `a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8`. /// * [`urn`](#method.to_urn): `urn:uuid:A1A2A3A4-B1B2-C1C2-D1D2-D3D4D5D6D7D8`. -/// * [`braced`](#method.braced): -/// `{a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8}`. +/// * [`braced`](#method.braced): `{a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8}`. /// /// The default representation when formatting a UUID with `Display` is /// hyphenated: @@ -745,6 +744,17 @@ impl AsRef<[u8]> for Uuid { } } +#[cfg(feature = "serde")] +pub mod serde { + //! Adapters for `serde`. + //! + //! This module contains adapters you can use with [`#[serde(with)]`](https://serde.rs/field-attrs.html#with) + //! to change the way a [`Uuid`](../struct.Uuid.html) is serialized + //! and deserialized. + + pub use crate::external::serde_support::compact; +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/parser.rs b/src/parser.rs index 641b979a..b3941937 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -66,12 +66,35 @@ impl Uuid { .map_err(InvalidUuid::into_err) } - /// Intended to replace `Uuid::parse_str` + /// Parses a `Uuid` from a string of hexadecimal digits with optional + /// hyphens. + /// + /// This function is similar to [`parse_str`], in fact `parse_str` shares + /// the same underlying parser. The difference is that if `try_parse` + /// fails, it won't generate very useful error messages. The `parse_str` + /// function will eventually be deprecated in favor or `try_parse`. + /// + /// # Examples + /// + /// Parse a hyphenated UUID: + /// + /// ``` + /// # use uuid::{Uuid, Version, Variant}; + /// # fn main() -> Result<(), Box> { + /// let uuid = Uuid::try_parse("550e8400-e29b-41d4-a716-446655440000")?; + /// + /// assert_eq!(Some(Version::Random), uuid.get_version()); + /// assert_eq!(Variant::RFC4122, uuid.get_variant()); + /// # Ok(()) + /// # } + /// ``` + /// + /// [`parse_str`]: #method.parse_str #[inline] - pub const fn try_parse(input: &str) -> Result { + pub const fn try_parse(input: &str) -> Result { match imp::try_parse(input) { Ok(bytes) => Ok(Uuid::from_bytes(bytes)), - Err(e) => Err(e), + Err(_) => Err(Error(ErrorKind::Other)), } } }