diff --git a/README.md b/README.md index a6740b39..9df9b948 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,8 @@ uuid --------- [![Latest Version](https://img.shields.io/crates/v/uuid.svg)](https://crates.io/crates/uuid) -[![Join the chat at https://gitter.im/uuid-rs/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/uuid-rs/Lobby?utm_source=badge&utm_medium=badge&utm_content=badge) ![Minimum rustc version](https://img.shields.io/badge/rustc-1.46.0+-yellow.svg) -[![Build Status](https://ci.appveyor.com/api/projects/status/github/uuid-rs/uuid?branch=main&svg=true)](https://ci.appveyor.com/project/uuid-rs/uuid/branch/main) -[![Build Status](https://travis-ci.org/uuid-rs/uuid.svg?branch=main)](https://travis-ci.org/uuid-rs/uuid) -[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/uuid-rs/uuid.svg)](https://isitmaintained.com/project/uuid-rs/uuid "Average time to resolve an issue") -[![Percentage of issues still open](https://isitmaintained.com/badge/open/uuid-rs/uuid.svg)](https://isitmaintained.com/project/uuid-rs/uuid "Percentage of issues still open") -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fuuid-rs%2Fuuid.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fuuid-rs%2Fuuid?ref=badge_shield) +[![Continuous integration](https://github.com/uuid-rs/uuid/actions/workflows/ci.yml/badge.svg)](https://github.com/uuid-rs/uuid/actions/workflows/ci.yml) --- @@ -28,6 +23,34 @@ The uniqueness property is not strictly guaranteed, however for all practical purposes, it can be assumed that an unintentional collision would be extremely unlikely. +## Getting started + +To get started with generating random UUIDs, add this to your `Cargo.toml`: + +```toml +[dependencies.uuid] +version = "0.8" +features = ["v4", "fast-rng"] +``` + +and then call `Uuid::new_v4` in your code: + +```rust +use uuid::Uuid; + +let my_uuid = Uuid::new_v4(); +``` + +You can also parse UUIDs without needing any crate features: + +```rust +use uuid::{Uuid, Version}; + +let my_uuid = Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8")?; + +assert_eq!(Some(Version::Random), my_uuid.get_version()); +``` + ## Dependencies By default, this crate depends on nothing but `std` and cannot generate @@ -58,27 +81,6 @@ You need to enable one of the following Cargo features together with the Alternatively, you can provide a custom `getrandom` implementation yourself via [`getrandom::register_custom_getrandom`](https://docs.rs/getrandom/0.2.2/getrandom/macro.register_custom_getrandom.html). -By default, `uuid` can be depended on with: - -```toml -[dependencies] -uuid = "0.8" -``` - -To activate various features, use syntax like: - -```toml -[dependencies] -uuid = { version = "0.8", features = ["serde", "v4"] } -``` - -You can disable default features with: - -```toml -[dependencies] -uuid = { version = "0.8", default-features = false } -``` - ### Unstable features Some features are unstable. They may be incomplete or depend on other unstable libraries. @@ -95,43 +97,6 @@ flag through your environment to opt-in to unstable `uuid` features: RUSTFLAGS="--cfg uuid_unstable" ``` -## Examples - -To parse a UUID given in the simple format and print it as a urn: - -```rust -use uuid::Uuid; - -fn main() -> Result<(), uuid::Error> { - let my_uuid = - Uuid::parse_str("936DA01F9ABD4d9d80C702AF85C822A8")?; - println!("{}", my_uuid.to_urn()); - Ok(()) -} -``` - -To create a new random (V4) UUID and print it out in hexadecimal form: - -```rust -// Note that this requires the `v4` feature enabled in the uuid crate. - -use uuid::Uuid; - -fn main() { - let my_uuid = Uuid::new_v4(); - println!("{}", my_uuid); - Ok(()) -} -``` - -## Strings - -Examples of string representations: - -* simple: `936DA01F9ABD4d9d80C702AF85C822A8` -* hyphenated: `550e8400-e29b-41d4-a716-446655440000` -* urn: `urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4` - ## References * [Wikipedia: Universally Unique Identifier]( http://en.wikipedia.org/wiki/Universally_unique_identifier) diff --git a/benches/v4.rs b/benches/v4.rs index 573fc690..61c3246b 100644 --- a/benches/v4.rs +++ b/benches/v4.rs @@ -1,5 +1,4 @@ #![cfg(feature = "v4")] - #![feature(test)] extern crate test; diff --git a/src/fmt.rs b/src/fmt.rs index 83367194..1d943b31 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -42,14 +42,22 @@ impl fmt::Display for Variant { impl fmt::LowerHex for Uuid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::LowerHex::fmt(&self.to_hyphenated_ref(), f) + if f.alternate() { + fmt::LowerHex::fmt(&self.to_simple_ref(), f) + } else { + fmt::LowerHex::fmt(&self.to_hyphenated_ref(), f) + } } } impl fmt::UpperHex for Uuid { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::UpperHex::fmt(&self.to_hyphenated_ref(), f) + if f.alternate() { + fmt::UpperHex::fmt(&self.to_simple_ref(), f) + } else { + fmt::UpperHex::fmt(&self.to_hyphenated_ref(), f) + } } } @@ -974,15 +982,13 @@ macro_rules! impl_fmt_traits { impl<$($a),*> fmt::LowerHex for $T<$($a),*> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: Self doesn't work https://github.com/rust-lang/rust/issues/52808 - f.write_str(self.encode_lower(&mut [0; $T::LENGTH])) + f.write_str(self.encode_lower(&mut [0; Self::LENGTH])) } } impl<$($a),*> fmt::UpperHex for $T<$($a),*> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: Self doesn't work https://github.com/rust-lang/rust/issues/52808 - f.write_str(self.encode_upper(&mut [0; $T::LENGTH])) + f.write_str(self.encode_upper(&mut [0; Self::LENGTH])) } } diff --git a/src/lib.rs b/src/lib.rs index 4c5dd0c5..9f178f01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,9 +42,9 @@ //! UUID based on the SHA1 hash of some data. //! * `serde` - adds the ability to serialize and deserialize a UUID using the //! `serde` crate. -//! * `fast-rng` - when combined with `v4` uses a faster algorithm for generating -//! random UUIDs. This feature requires more dependencies to compile, but is just -//! as suitable for UUIDs as the default algorithm. +//! * `fast-rng` - when combined with `v4` uses a faster algorithm for +//! generating random UUIDs. This feature requires more dependencies to +//! compile, but is just as suitable for UUIDs as the default algorithm. //! //! By default, `uuid` can be depended on with: //! @@ -818,19 +818,6 @@ mod tests { || c == '-'); } - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] - fn test_uuid_upperhex() { - use crate::std::fmt::Write; - - let mut buffer = String::new(); - let uuid = new(); - - check!(buffer, "{:X}", uuid, 36, |c| c.is_uppercase() - || c.is_digit(10) - || c == '-'); - } - #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn test_nil() { @@ -936,25 +923,43 @@ mod tests { ($buf:ident, $format:expr, $target:expr, $len:expr, $cond:expr) => { $buf.clear(); write!($buf, $format, $target).unwrap(); - assert!(buf.len() == $len); + assert_eq!($len, buf.len()); assert!($buf.chars().all($cond), "{}", $buf); }; } + check!(buf, "{:x}", u, 36, |c| c.is_lowercase() + || c.is_digit(10) + || c == '-'); check!(buf, "{:X}", u, 36, |c| c.is_uppercase() || c.is_digit(10) || c == '-'); + check!(buf, "{:#x}", u, 32, |c| c.is_lowercase() + || c.is_digit(10)); + check!(buf, "{:#X}", u, 32, |c| c.is_uppercase() + || c.is_digit(10)); + check!(buf, "{:X}", u.to_hyphenated(), 36, |c| c.is_uppercase() || c.is_digit(10) || c == '-'); check!(buf, "{:X}", u.to_simple(), 32, |c| c.is_uppercase() || c.is_digit(10)); + check!(buf, "{:#X}", u.to_hyphenated(), 36, |c| c.is_uppercase() + || c.is_digit(10) + || c == '-'); + check!(buf, "{:#X}", u.to_simple(), 32, |c| c.is_uppercase() + || c.is_digit(10)); check!(buf, "{:x}", u.to_hyphenated(), 36, |c| c.is_lowercase() || c.is_digit(10) || c == '-'); check!(buf, "{:x}", u.to_simple(), 32, |c| c.is_lowercase() || c.is_digit(10)); + check!(buf, "{:#x}", u.to_hyphenated(), 36, |c| c.is_lowercase() + || c.is_digit(10) + || c == '-'); + check!(buf, "{:#x}", u.to_simple(), 32, |c| c.is_lowercase() + || c.is_digit(10)); } #[test] diff --git a/src/parser.rs b/src/parser.rs index 44f3e88d..842c680f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -13,7 +13,11 @@ //! //! [`Uuid`]: ../struct.Uuid.html -use crate::{error::*, std::str, Uuid}; +use crate::{ + error::*, + std::{convert::TryFrom, str}, + Uuid, +}; #[path = "../shared/parser.rs"] mod imp; @@ -26,6 +30,14 @@ impl str::FromStr for Uuid { } } +impl TryFrom<&'_ str> for Uuid { + type Error = Error; + + fn try_from(uuid_str: &'_ str) -> Result { + Uuid::parse_str(uuid_str) + } +} + impl Uuid { /// Parses a `Uuid` from a string of hexadecimal digits with optional /// hyphens. diff --git a/src/serde_support.rs b/src/serde_support.rs index 6fb1d80a..41c2eda0 100644 --- a/src/serde_support.rs +++ b/src/serde_support.rs @@ -9,8 +9,16 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::{std::fmt, Uuid}; -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use crate::{ + error::*, + fmt::{Hyphenated, HyphenatedRef, Simple, SimpleRef, Urn, UrnRef}, + std::fmt, + Uuid, +}; +use serde::{ + de::{self, Error as _}, + Deserialize, Deserializer, Serialize, Serializer, +}; impl Serialize for Uuid { fn serialize( @@ -18,26 +26,82 @@ impl Serialize for Uuid { serializer: S, ) -> Result { if serializer.is_human_readable() { - serializer - .serialize_str(self.to_hyphenated().encode_lower(&mut [0; 36])) + serializer.serialize_str( + self.to_hyphenated() + .encode_lower(&mut Uuid::encode_buffer()), + ) } else { self.as_bytes().serialize(serializer) } } } +impl Serialize for Hyphenated { + fn serialize( + &self, + serializer: S, + ) -> Result { + serializer.serialize_str(self.encode_lower(&mut Uuid::encode_buffer())) + } +} + +impl Serialize for HyphenatedRef<'_> { + fn serialize( + &self, + serializer: S, + ) -> Result { + serializer.serialize_str(self.encode_lower(&mut Uuid::encode_buffer())) + } +} + +impl Serialize for Simple { + fn serialize( + &self, + serializer: S, + ) -> Result { + serializer.serialize_str(self.encode_lower(&mut Uuid::encode_buffer())) + } +} + +impl Serialize for SimpleRef<'_> { + fn serialize( + &self, + serializer: S, + ) -> Result { + serializer.serialize_str(self.encode_lower(&mut Uuid::encode_buffer())) + } +} + +impl Serialize for Urn { + fn serialize( + &self, + serializer: S, + ) -> Result { + serializer.serialize_str(self.encode_lower(&mut Uuid::encode_buffer())) + } +} + +impl Serialize for UrnRef<'_> { + fn serialize( + &self, + serializer: S, + ) -> Result { + serializer.serialize_str(self.encode_lower(&mut Uuid::encode_buffer())) + } +} + impl<'de> Deserialize<'de> for Uuid { fn deserialize>( deserializer: D, ) -> Result { - fn de_error(e: crate::Error) -> E { + fn de_error(e: Error) -> E { E::custom(format_args!("UUID parsing failed: {}", e)) } if deserializer.is_human_readable() { - struct UuidStringVisitor; + struct UuidVisitor; - impl<'vi> de::Visitor<'vi> for UuidStringVisitor { + impl<'vi> de::Visitor<'vi> for UuidVisitor { type Value = Uuid; fn expecting( @@ -60,9 +124,36 @@ impl<'de> Deserialize<'de> for Uuid { ) -> Result { Uuid::from_slice(value).map_err(de_error) } + + fn visit_seq(self, mut seq: A) -> Result + where + A: de::SeqAccess<'vi>, + { + #[rustfmt::skip] + let bytes = [ + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + match seq.next_element()? { Some(e) => e, None => return Err(A::Error::invalid_length(16, &self)) }, + ]; + + Ok(Uuid::from_bytes(bytes)) + } } - deserializer.deserialize_str(UuidStringVisitor) + deserializer.deserialize_str(UuidVisitor) } else { let bytes: [u8; 16] = Deserialize::deserialize(deserializer)?; @@ -78,12 +169,77 @@ mod serde_tests { use serde_test::{Compact, Configure, Readable, Token}; #[test] - fn test_serialize_readable() { + fn test_serialize_readable_string() { let uuid_str = "f9168c5e-ceb2-4faa-b6bf-329bf39fa1e4"; let u = Uuid::parse_str(uuid_str).unwrap(); serde_test::assert_tokens(&u.readable(), &[Token::Str(uuid_str)]); } + #[test] + fn test_deserialize_readable_compact() { + let uuid_bytes = b"F9168C5E-CEB2-4F"; + let u = Uuid::from_slice(uuid_bytes).unwrap(); + + serde_test::assert_de_tokens( + &u.readable(), + &[ + 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, + ], + ); + } + + #[test] + fn test_deserialize_readable_bytes() { + let uuid_bytes = b"F9168C5E-CEB2-4F"; + let u = Uuid::from_slice(uuid_bytes).unwrap(); + + serde_test::assert_de_tokens( + &u.readable(), + &[serde_test::Token::Bytes(uuid_bytes)], + ); + } + + #[test] + 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.to_hyphenated(), + &[Token::Str(uuid_str)], + ); + } + + #[test] + fn test_serialize_simple() { + let uuid_str = "f9168c5eceb24faab6bf329bf39fa1e4"; + let u = Uuid::parse_str(uuid_str).unwrap(); + serde_test::assert_ser_tokens(&u.to_simple(), &[Token::Str(uuid_str)]); + } + + #[test] + fn test_serialize_urn() { + let uuid_str = "urn:uuid:f9168c5e-ceb2-4faa-b6bf-329bf39fa1e4"; + let u = Uuid::parse_str(uuid_str).unwrap(); + serde_test::assert_ser_tokens(&u.to_urn(), &[Token::Str(uuid_str)]); + } + #[test] fn test_serialize_compact() { let uuid_bytes = b"F9168C5E-CEB2-4F";