Skip to content

Commit

Permalink
Merge pull request #541 from KodrAus/feat/macros-scaffold
Browse files Browse the repository at this point in the history
Scaffold out a uuid-macros crate that shares the parser
  • Loading branch information
KodrAus committed Oct 31, 2021
2 parents 66a544d + 817260a commit 3ff12ee
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 301 deletions.
11 changes: 11 additions & 0 deletions Cargo.toml
Expand Up @@ -55,6 +55,7 @@ repository = "uuid-rs/uuid"
default = ["std"]
guid = ["winapi"]
std = []
macros = ["uuid-macros"]
v1 = ["atomic"]
v3 = ["md-5"]
v4 = ["getrandom"]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -130,3 +136,8 @@ version = "0.3"
[target.'cfg(windows)'.dev-dependencies.winapi]
version = "0.3"
features = ["combaseapi"]

[workspace]
members = [
"macros"
]
9 changes: 9 additions & 0 deletions macros/Cargo.toml
@@ -0,0 +1,9 @@
[package]
name = "uuid-macros"
version = "0.0.0"
edition = "2018"

[lib]
proc-macro = true

[dependencies]
17 changes: 17 additions & 0 deletions 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);
}
}
3 changes: 3 additions & 0 deletions 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`.
148 changes: 148 additions & 0 deletions 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<ErrorKind> 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 {}
}
167 changes: 167 additions & 0 deletions 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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)
}

0 comments on commit 3ff12ee

Please sign in to comment.