Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scaffold out a uuid-macros crate that shares the parser #541

Merged
merged 1 commit into from Oct 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
}