diff --git a/Cargo.toml b/Cargo.toml index d8a4da8a..18ffc877 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ repository = "uuid-rs/uuid" default = ["std"] guid = ["winapi"] std = [] +macros = ["uuid-macros"] v1 = ["atomic"] v3 = ["md-5"] v4 = ["getrandom"] @@ -87,6 +88,11 @@ default-features = false optional = true version = "0.9" +# Public: Re-expored +[dependencies.uuid-macros] +path = "macros" +optional = true + # Public: Used in trait impls on `Uuid` [dependencies.serde] default-features = false @@ -130,3 +136,8 @@ version = "0.3" [target.'cfg(windows)'.dev-dependencies.winapi] version = "0.3" features = ["combaseapi"] + +[workspace] +members = [ + "macros" +] diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 00000000..75f04119 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "uuid-macros" +version = "0.0.0" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 00000000..396a74a4 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,17 @@ +use std; + +#[path = "../../shared/error.rs"] +#[allow(dead_code)] +mod error; + +#[path = "../../shared/parser.rs"] +#[allow(dead_code)] +mod parser; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/shared/README.md b/shared/README.md new file mode 100644 index 00000000..21cedd50 --- /dev/null +++ b/shared/README.md @@ -0,0 +1,3 @@ +# Shared source + +This source is shared between `uuid` and `uuid-macros` to avoid circular dependencies or creating additional dependencies for `uuid`. diff --git a/shared/error.rs b/shared/error.rs new file mode 100644 index 00000000..63010fcc --- /dev/null +++ b/shared/error.rs @@ -0,0 +1,148 @@ +use crate::std::fmt; + +/// A general error that can occur when working with UUIDs. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Error(pub(crate) ErrorKind); + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub(crate) enum ErrorKind { + /// Invalid character in the [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + InvalidCharacter { + /// The expected characters. + expected: &'static str, + /// The invalid character found. + found: char, + /// The invalid character position. + index: usize, + /// Indicates the [`Uuid`] starts with `urn:uuid:`. + /// + /// This is a special case for [`Urn`] adapter parsing. + /// + /// [`Uuid`]: ../Uuid.html + urn: UrnPrefix, + }, + /// Invalid number of segments in the [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + InvalidGroupCount { + /// The expected number of segments. + expected: ExpectedLength, + /// The number of segments found. + found: usize, + }, + /// Invalid length of a segment in a [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + InvalidGroupLength { + /// The expected length of the segment. + expected: ExpectedLength, + /// The length of segment found. + found: usize, + /// The segment with invalid length. + group: usize, + }, + /// Invalid length of the [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + InvalidLength { + /// The expected length(s). + expected: ExpectedLength, + /// The invalid length found. + found: usize, + }, +} + +/// The expected length. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub(crate) enum ExpectedLength { + /// Expected any one of the given values. + Any(&'static [usize]), + /// Expected the given value. + Exact(usize), +} + +/// Urn prefix value. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub(crate) enum UrnPrefix { + /// The `urn:uuid:` prefix should optionally provided. + Optional, +} + +impl fmt::Display for ExpectedLength { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + ExpectedLength::Any(crits) => write!(f, "one of {:?}", crits), + ExpectedLength::Exact(crit) => write!(f, "{}", crit), + } + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Error(kind) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}: ", + match self.0 { + ErrorKind::InvalidCharacter { .. } => "invalid character", + ErrorKind::InvalidGroupCount { .. } => + "invalid number of groups", + ErrorKind::InvalidGroupLength { .. } => "invalid group length", + ErrorKind::InvalidLength { .. } => "invalid length", + } + )?; + + match self.0 { + ErrorKind::InvalidCharacter { + expected, + found, + index, + urn, + } => { + let urn_str = match urn { + UrnPrefix::Optional => { + " an optional prefix of `urn:uuid:` followed by" + } + }; + + write!( + f, + "expected{} {}, found {} at {}", + urn_str, expected, found, index + ) + } + ErrorKind::InvalidGroupCount { + ref expected, + found, + } => write!(f, "expected {}, found {}", expected, found), + ErrorKind::InvalidGroupLength { + ref expected, + found, + group, + } => write!( + f, + "expected {}, found {} in group {}", + expected, found, group, + ), + ErrorKind::InvalidLength { + ref expected, + found, + } => write!(f, "expected {}, found {}", expected, found), + } + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + use crate::std::error; + + impl error::Error for Error {} +} diff --git a/shared/parser.rs b/shared/parser.rs new file mode 100644 index 00000000..0eb27017 --- /dev/null +++ b/shared/parser.rs @@ -0,0 +1,167 @@ +// Copyright 2013-2014 The Rust Project Developers. +// Copyright 2018 The Uuid Project Developers. +// +// See the COPYRIGHT file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::{error::*, std::str}; + +/// Check if the length matches any of the given criteria lengths. +fn len_matches_any(len: usize, crits: &[usize]) -> bool { + for crit in crits { + if len == *crit { + return true; + } + } + + false +} + +// Accumulated length of each hyphenated group in hex digits. +const ACC_GROUP_LENS: [usize; 5] = [8, 12, 16, 20, 32]; + +// Length of each hyphenated group in hex digits. +const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; + +pub fn parse_str(mut input: &str) -> Result<[u8; 16], Error> { + // Ensure length is valid for any of the supported formats + let len = input.len(); + + if len == 45 && input.starts_with("urn:uuid:") { + input = &input[9..]; + } else if !len_matches_any( + len, + &[36, 32], + ) { + return Err(ErrorKind::InvalidLength { + expected: ExpectedLength::Any(&[ + 36, + 32, + ]), + found: len, + } + .into()); + } + + // `digit` counts only hexadecimal digits, `i_char` counts all chars. + let mut digit = 0; + let mut group = 0; + let mut acc = 0; + let mut buffer = [0u8; 16]; + + for (i_char, chr) in input.bytes().enumerate() { + if digit as usize >= 32 && group != 4 { + if group == 0 { + return Err(ErrorKind::InvalidLength { + expected: ExpectedLength::Any(&[ + 36, + 32, + ]), + found: len, + } + .into()); + } + + return Err(ErrorKind::InvalidGroupCount { + expected: ExpectedLength::Any(&[1, 5]), + found: group + 1, + } + .into()); + } + + if digit % 2 == 0 { + // First digit of the byte. + match chr { + // Calculate upper half. + b'0'..=b'9' => acc = chr - b'0', + b'a'..=b'f' => acc = chr - b'a' + 10, + b'A'..=b'F' => acc = chr - b'A' + 10, + // Found a group delimiter + b'-' => { + if ACC_GROUP_LENS[group] as u8 != digit { + // Calculate how many digits this group consists of + // in the input. + let found = if group > 0 { + digit - ACC_GROUP_LENS[group - 1] as u8 + } else { + digit + }; + + return Err(ErrorKind::InvalidGroupLength { + expected: ExpectedLength::Exact( + GROUP_LENS[group], + ), + found: found as usize, + group, + } + .into()); + } + // Next group, decrement digit, it is incremented again + // at the bottom. + group += 1; + digit -= 1; + } + _ => { + return Err(ErrorKind::InvalidCharacter { + expected: "0123456789abcdefABCDEF-", + found: input[i_char..].chars().next().unwrap(), + index: i_char, + urn: UrnPrefix::Optional, + } + .into()); + } + } + } else { + // Second digit of the byte, shift the upper half. + acc *= 16; + match chr { + b'0'..=b'9' => acc += chr - b'0', + b'a'..=b'f' => acc += chr - b'a' + 10, + b'A'..=b'F' => acc += chr - b'A' + 10, + b'-' => { + // The byte isn't complete yet. + let found = if group > 0 { + digit - ACC_GROUP_LENS[group - 1] as u8 + } else { + digit + }; + + return Err(ErrorKind::InvalidGroupLength { + expected: ExpectedLength::Exact(GROUP_LENS[group]), + found: found as usize, + group, + } + .into()); + } + _ => { + return Err(ErrorKind::InvalidCharacter { + expected: "0123456789abcdefABCDEF-", + found: input[i_char..].chars().next().unwrap(), + index: i_char, + urn: UrnPrefix::Optional, + } + .into()); + } + } + buffer[(digit / 2) as usize] = acc; + } + digit += 1; + } + + // Now check the last group. + if ACC_GROUP_LENS[4] as u8 != digit { + return Err(ErrorKind::InvalidGroupLength { + expected: ExpectedLength::Exact(GROUP_LENS[4]), + found: (digit as usize - ACC_GROUP_LENS[3]), + group, + } + .into()); + } + + Ok(buffer) +} diff --git a/src/error.rs b/src/error.rs index 63010fcc..2e2f55e9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,148 +1,4 @@ -use crate::std::fmt; +#[path = "../shared/error.rs"] +mod imp; -/// A general error that can occur when working with UUIDs. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct Error(pub(crate) ErrorKind); - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub(crate) enum ErrorKind { - /// Invalid character in the [`Uuid`] string. - /// - /// [`Uuid`]: ../struct.Uuid.html - InvalidCharacter { - /// The expected characters. - expected: &'static str, - /// The invalid character found. - found: char, - /// The invalid character position. - index: usize, - /// Indicates the [`Uuid`] starts with `urn:uuid:`. - /// - /// This is a special case for [`Urn`] adapter parsing. - /// - /// [`Uuid`]: ../Uuid.html - urn: UrnPrefix, - }, - /// Invalid number of segments in the [`Uuid`] string. - /// - /// [`Uuid`]: ../struct.Uuid.html - InvalidGroupCount { - /// The expected number of segments. - expected: ExpectedLength, - /// The number of segments found. - found: usize, - }, - /// Invalid length of a segment in a [`Uuid`] string. - /// - /// [`Uuid`]: ../struct.Uuid.html - InvalidGroupLength { - /// The expected length of the segment. - expected: ExpectedLength, - /// The length of segment found. - found: usize, - /// The segment with invalid length. - group: usize, - }, - /// Invalid length of the [`Uuid`] string. - /// - /// [`Uuid`]: ../struct.Uuid.html - InvalidLength { - /// The expected length(s). - expected: ExpectedLength, - /// The invalid length found. - found: usize, - }, -} - -/// The expected length. -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub(crate) enum ExpectedLength { - /// Expected any one of the given values. - Any(&'static [usize]), - /// Expected the given value. - Exact(usize), -} - -/// Urn prefix value. -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub(crate) enum UrnPrefix { - /// The `urn:uuid:` prefix should optionally provided. - Optional, -} - -impl fmt::Display for ExpectedLength { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - ExpectedLength::Any(crits) => write!(f, "one of {:?}", crits), - ExpectedLength::Exact(crit) => write!(f, "{}", crit), - } - } -} - -impl From for Error { - fn from(kind: ErrorKind) -> Self { - Error(kind) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}: ", - match self.0 { - ErrorKind::InvalidCharacter { .. } => "invalid character", - ErrorKind::InvalidGroupCount { .. } => - "invalid number of groups", - ErrorKind::InvalidGroupLength { .. } => "invalid group length", - ErrorKind::InvalidLength { .. } => "invalid length", - } - )?; - - match self.0 { - ErrorKind::InvalidCharacter { - expected, - found, - index, - urn, - } => { - let urn_str = match urn { - UrnPrefix::Optional => { - " an optional prefix of `urn:uuid:` followed by" - } - }; - - write!( - f, - "expected{} {}, found {} at {}", - urn_str, expected, found, index - ) - } - ErrorKind::InvalidGroupCount { - ref expected, - found, - } => write!(f, "expected {}, found {}", expected, found), - ErrorKind::InvalidGroupLength { - ref expected, - found, - group, - } => write!( - f, - "expected {}, found {} in group {}", - expected, found, group, - ), - ErrorKind::InvalidLength { - ref expected, - found, - } => write!(f, "expected {}, found {}", expected, found), - } - } -} - -#[cfg(feature = "std")] -mod std_support { - use super::*; - use crate::std::error; - - impl error::Error for Error {} -} +pub use self::imp::*; diff --git a/src/lib.rs b/src/lib.rs index 4e1af4cc..ff41f718 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -232,6 +232,10 @@ mod v5; #[cfg(all(windows, feature = "winapi"))] mod winapi_support; +#[cfg(feature = "macros")] +#[doc(inline)] +pub use uuid_macros::*; + use crate::std::convert; pub use crate::{builder::Builder, error::Error}; diff --git a/src/parser.rs b/src/parser.rs index 1dec388b..04ebd3f8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -13,7 +13,10 @@ //! //! [`Uuid`]: ../struct.Uuid.html -use crate::{error::*, fmt, std::str, Uuid}; +use crate::{error::*, std::str, Uuid}; + +#[path = "../shared/parser.rs"] +mod imp; impl str::FromStr for Uuid { type Err = Error; @@ -23,165 +26,14 @@ impl str::FromStr for Uuid { } } -/// Check if the length matches any of the given criteria lengths. -fn len_matches_any(len: usize, crits: &[usize]) -> bool { - for crit in crits { - if len == *crit { - return true; - } - } - - false -} - -// Accumulated length of each hyphenated group in hex digits. -const ACC_GROUP_LENS: [usize; 5] = [8, 12, 16, 20, 32]; - -// Length of each hyphenated group in hex digits. -const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; - impl Uuid { /// Parses a `Uuid` from a string of hexadecimal digits with optional /// hyphens. /// /// Any of the formats generated by this module (simple, hyphenated, urn) /// are supported by this parsing function. - pub fn parse_str(mut input: &str) -> Result { - // Ensure length is valid for any of the supported formats - let len = input.len(); - - if len == fmt::Urn::LENGTH && input.starts_with("urn:uuid:") { - input = &input[9..]; - } else if !len_matches_any( - len, - &[fmt::Hyphenated::LENGTH, fmt::Simple::LENGTH], - ) { - return Err(ErrorKind::InvalidLength { - expected: ExpectedLength::Any(&[ - fmt::Hyphenated::LENGTH, - fmt::Simple::LENGTH, - ]), - found: len, - } - .into()); - } - - // `digit` counts only hexadecimal digits, `i_char` counts all chars. - let mut digit = 0; - let mut group = 0; - let mut acc = 0; - let mut buffer = [0u8; 16]; - - for (i_char, chr) in input.bytes().enumerate() { - if digit as usize >= fmt::Simple::LENGTH && group != 4 { - if group == 0 { - return Err(ErrorKind::InvalidLength { - expected: ExpectedLength::Any(&[ - fmt::Hyphenated::LENGTH, - fmt::Simple::LENGTH, - ]), - found: len, - } - .into()); - } - - return Err(ErrorKind::InvalidGroupCount { - expected: ExpectedLength::Any(&[1, 5]), - found: group + 1, - } - .into()); - } - - if digit % 2 == 0 { - // First digit of the byte. - match chr { - // Calculate upper half. - b'0'..=b'9' => acc = chr - b'0', - b'a'..=b'f' => acc = chr - b'a' + 10, - b'A'..=b'F' => acc = chr - b'A' + 10, - // Found a group delimiter - b'-' => { - if ACC_GROUP_LENS[group] as u8 != digit { - // Calculate how many digits this group consists of - // in the input. - let found = if group > 0 { - digit - ACC_GROUP_LENS[group - 1] as u8 - } else { - digit - }; - - return Err(ErrorKind::InvalidGroupLength { - expected: ExpectedLength::Exact( - GROUP_LENS[group], - ), - found: found as usize, - group, - } - .into()); - } - // Next group, decrement digit, it is incremented again - // at the bottom. - group += 1; - digit -= 1; - } - _ => { - return Err(ErrorKind::InvalidCharacter { - expected: "0123456789abcdefABCDEF-", - found: input[i_char..].chars().next().unwrap(), - index: i_char, - urn: UrnPrefix::Optional, - } - .into()); - } - } - } else { - // Second digit of the byte, shift the upper half. - acc *= 16; - match chr { - b'0'..=b'9' => acc += chr - b'0', - b'a'..=b'f' => acc += chr - b'a' + 10, - b'A'..=b'F' => acc += chr - b'A' + 10, - b'-' => { - // The byte isn't complete yet. - let found = if group > 0 { - digit - ACC_GROUP_LENS[group - 1] as u8 - } else { - digit - }; - - return Err(ErrorKind::InvalidGroupLength { - expected: ExpectedLength::Exact(GROUP_LENS[group]), - found: found as usize, - group, - } - .into()); - } - _ => { - return Err(ErrorKind::InvalidCharacter { - expected: "0123456789abcdefABCDEF-", - found: input[i_char..].chars().next().unwrap(), - index: i_char, - urn: UrnPrefix::Optional, - } - .into()); - } - } - buffer[(digit / 2) as usize] = acc; - } - digit += 1; - } - - // Now check the last group. - if ACC_GROUP_LENS[4] as u8 != digit { - return Err(ErrorKind::InvalidGroupLength { - expected: ExpectedLength::Exact(GROUP_LENS[4]), - found: (digit as usize - ACC_GROUP_LENS[3]), - group, - } - .into()); - } - - Ok(Uuid::from_bytes(buffer)) + pub fn parse_str(input: &str) -> Result { + Ok(Uuid::from_bytes(imp::parse_str(input)?)) } }