Skip to content

Commit

Permalink
Merge pull request #543 from QnnOkabayashi/impl-uuid-macro
Browse files Browse the repository at this point in the history
Impl UUID macro
  • Loading branch information
KodrAus committed Nov 1, 2021
2 parents b0fcdac + d6aed32 commit fe7bbc7
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 11 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Expand Up @@ -144,6 +144,9 @@ version = "0.2"
[dev-dependencies.wasm-bindgen-test]
version = "0.3"

[dev-dependencies.trybuild]
version = "1.0.52"

[target.'cfg(windows)'.dev-dependencies.winapi]
version = "0.3"
features = ["combaseapi"]
Expand Down
29 changes: 29 additions & 0 deletions benches/macros/invalid_parse.rs
@@ -0,0 +1,29 @@
use uuid::{uuid, Uuid};

const _: Uuid = uuid!("");
const _: Uuid = uuid!("!");
const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45");
const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4");
const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4");
const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4");
const _: Uuid = uuid!("F9168C5E-CEB2-4faa");
const _: Uuid = uuid!("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4");
const _: Uuid = uuid!("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4");
const _: Uuid = uuid!("01020304-1112-2122-3132-41424344");
const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c88");
const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0cg8");
const _: Uuid = uuid!("67e5504410b1426%9247bb680e5fe0c8");

// Test error reporting
const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c");
const _: Uuid = uuid!("67e550X410b1426f9247bb680e5fe0cd");
const _: Uuid = uuid!("67e550-4105b1426f9247bb680e5fe0c");
const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4");


const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4");
const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4");
const _: Uuid = uuid!("01020304-1112-2122-3132-41424344");
const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4");

fn main() {}
125 changes: 125 additions & 0 deletions benches/macros/invalid_parse.stderr
@@ -0,0 +1,125 @@
error: invalid length: expected one of [36, 32], found 0
--> benches/macros/invalid_parse.rs:3:23
|
3 | const _: Uuid = uuid!("");
| ^^

error: invalid length: expected one of [36, 32], found 1
--> benches/macros/invalid_parse.rs:4:23
|
4 | const _: Uuid = uuid!("!");
| ^^^

error: invalid length: expected one of [36, 32], found 37
--> benches/macros/invalid_parse.rs:5:23
|
5 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: invalid length: expected one of [36, 32], found 35
--> benches/macros/invalid_parse.rs:6:23
|
6 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found G at 20
--> benches/macros/invalid_parse.rs:7:44
|
7 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4");
| ^

error: invalid number of groups: expected one of [1, 5], found 4
--> benches/macros/invalid_parse.rs:8:23
|
8 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: invalid length: expected one of [36, 32], found 18
--> benches/macros/invalid_parse.rs:9:23
|
9 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa");
| ^^^^^^^^^^^^^^^^^^^^

error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found X at 18
--> benches/macros/invalid_parse.rs:10:42
|
10 | const _: Uuid = uuid!("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4");
| ^

error: invalid group length: expected 4, found 3 in group 1
--> benches/macros/invalid_parse.rs:11:33
|
11 | const _: Uuid = uuid!("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4");
| ^^^

error: invalid group length: expected 12, found 8 in group 4
--> benches/macros/invalid_parse.rs:12:48
|
12 | const _: Uuid = uuid!("01020304-1112-2122-3132-41424344");
| ^^^^^^^^

error: invalid length: expected one of [36, 32], found 33
--> benches/macros/invalid_parse.rs:13:23
|
13 | const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c88");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: invalid length: expected one of [36, 32], found 33
--> benches/macros/invalid_parse.rs:14:23
|
14 | const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0cg8");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found % at 15
--> benches/macros/invalid_parse.rs:15:39
|
15 | const _: Uuid = uuid!("67e5504410b1426%9247bb680e5fe0c8");
| ^

error: invalid length: expected one of [36, 32], found 31
--> benches/macros/invalid_parse.rs:18:23
|
18 | const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found X at 6
--> benches/macros/invalid_parse.rs:19:30
|
19 | const _: Uuid = uuid!("67e550X410b1426f9247bb680e5fe0cd");
| ^

error: invalid group length: expected 8, found 6 in group 0
--> benches/macros/invalid_parse.rs:20:24
|
20 | const _: Uuid = uuid!("67e550-4105b1426f9247bb680e5fe0c");
| ^^^^^^

error: invalid group length: expected 4, found 5 in group 3
--> benches/macros/invalid_parse.rs:21:43
|
21 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4");
| ^^^^^

error: invalid length: expected one of [36, 32], found 35
--> benches/macros/invalid_parse.rs:24:23
|
24 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found G at 20
--> benches/macros/invalid_parse.rs:25:44
|
25 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4");
| ^

error: invalid group length: expected 12, found 8 in group 4
--> benches/macros/invalid_parse.rs:26:48
|
26 | const _: Uuid = uuid!("01020304-1112-2122-3132-41424344");
| ^^^^^^^^

error: invalid number of groups: expected one of [1, 5], found 4
--> benches/macros/invalid_parse.rs:27:23
|
27 | const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 changes: 11 additions & 0 deletions benches/macros/renamed.rs
@@ -0,0 +1,11 @@
use ::uuid::{uuid as id, Uuid as Id};

mod uuid {
struct MyType;
}

struct Uuid;

const _: Id = id!("67e55044-10b1-426f-9247-bb680e5fe0c8");

fn main() {}
20 changes: 20 additions & 0 deletions benches/macros/valid_parse.rs
@@ -0,0 +1,20 @@
use uuid::{uuid, Uuid};

const _: Uuid = uuid!("00000000000000000000000000000000");
const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8");
const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8");
const _: Uuid = uuid!("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4");
const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c8");
const _: Uuid = uuid!("01020304-1112-2122-3132-414243444546");
const _: Uuid = uuid!("urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8");

// Nil
const _: Uuid = uuid!("00000000000000000000000000000000");
const _: Uuid = uuid!("00000000-0000-0000-0000-000000000000");

// valid hyphenated
const _: Uuid = uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8");
// valid short
const _: Uuid = uuid!("67e5504410b1426f9247bb680e5fe0c8");

fn main() {}
9 changes: 9 additions & 0 deletions benches/parse_str.rs
Expand Up @@ -48,3 +48,12 @@ fn parse_invalid_group_len(b: &mut Bencher) {
fn parse_invalid_groups(b: &mut Bencher) {
b.iter(|| Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"));
}

#[cfg(feature = "macros")]
#[test]
fn test_valid_macro() {
let t = trybuild::TestCases::new();
t.pass("benches/macros/valid_parse.rs");
t.pass("benches/macros/renamed.rs");
t.compile_fail("benches/macros/invalid_parse.rs");
}
3 changes: 3 additions & 0 deletions macros/Cargo.toml
Expand Up @@ -7,3 +7,6 @@ edition = "2018"
proc-macro = true

[dependencies]
syn = "1.0.80"
quote = "1.0.10"
proc-macro2 = "1.0.29"
80 changes: 72 additions & 8 deletions macros/src/lib.rs
@@ -1,17 +1,81 @@
use std;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned};
use std::fmt;
use syn::spanned::Spanned;

#[cfg(any(feature = "std", test))]
#[macro_use]
extern crate std;

#[cfg(all(not(feature = "std"), not(test)))]
#[macro_use]
extern crate core as 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);
#[proc_macro]
pub fn parse_lit(input: TokenStream) -> TokenStream {
build_uuid(input.clone()).unwrap_or_else(|e| {
let msg = e.to_string();
let ts = TokenStream2::from(input);
let span = match e {
Error::UuidParse(error::Error(
error::ErrorKind::InvalidCharacter { index, .. },
)) => {
let mut s = proc_macro2::Literal::string("");
s.set_span(ts.span());
s.subspan(index + 1..=index + 1).unwrap()
}
Error::UuidParse(error::Error(
error::ErrorKind::InvalidGroupLength { found, group, .. },
)) => {
let start =
parser::GROUP_LENS.iter().take(group).sum::<usize>()
+ group
+ 1;
let mut s = proc_macro2::Literal::string("");
s.set_span(ts.span());
s.subspan(start..start + found).unwrap()
}
_ => ts.span(),
};
TokenStream::from(quote_spanned! {span=>
compile_error!(#msg)
})
})
}

enum Error {
NonStringLiteral,
UuidParse(error::Error),
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::NonStringLiteral => f.write_str("expected string literal"),
Error::UuidParse(ref e) => write!(f, "{}", e),
}
}
}

fn build_uuid(input: TokenStream) -> Result<TokenStream, Error> {
let string = match syn::parse::<syn::Lit>(input) {
Ok(syn::Lit::Str(literal)) => literal.value(),
_ => return Err(Error::NonStringLiteral),
};

let bytes = parser::parse_str(&string).map_err(Error::UuidParse)?;

let tokens = bytes
.iter()
.map(|byte| quote! { #byte, })
.collect::<TokenStream2>();

Ok(quote! {[#tokens]}.into())
}
2 changes: 1 addition & 1 deletion shared/parser.rs
Expand Up @@ -26,7 +26,7 @@ fn len_matches_any(len: usize, crits: &[usize]) -> bool {
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(super) 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
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Expand Up @@ -213,8 +213,10 @@ mod slog_support;
mod winapi_support;

#[cfg(feature = "macros")]
#[doc(inline)]
pub use uuid_macros::*;
#[macro_use]
mod macros;
#[cfg(feature = "macros")]
pub extern crate uuid_macros;

use crate::std::convert;

Expand Down
53 changes: 53 additions & 0 deletions src/macros.rs
@@ -0,0 +1,53 @@
/// Parse [`Uuid`][uuid::Uuid]s from string literals at compile time.
/// ## Usage
/// This macro transforms the string literal representation of a
/// [`Uuid`][uuid::Uuid] into the bytes representation, raising a compilation
/// error if it cannot properly be parsed.
///
/// ## Examples
/// Setting a global constant:
/// ```
/// # use uuid::{uuid, Uuid};
/// pub const SCHEMA_ATTR_CLASS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000000");
/// pub const SCHEMA_ATTR_UUID: Uuid = uuid!("00000000-0000-0000-0000-ffff00000001");
/// pub const SCHEMA_ATTR_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000002");
/// ```
/// Defining a local variable:
/// ```
/// # use uuid::{uuid, Uuid};
/// let uuid: Uuid = uuid!("urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4");
/// ```
/// ## Compilation Failures
/// Invalid UUIDs are rejected:
/// ```ignore
/// # use uuid::{uuid, Uuid};
/// let uuid: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4");
/// ```
/// Provides the following compilation error:
/// ```txt
/// error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found Z at 9
/// |
/// | let id: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4");
/// | ^
/// ```
/// Tokens that aren't string literals are also rejected:
/// ```ignore
/// # use uuid::{uuid, Uuid};
/// let uuid_str: &str = "550e8400e29b41d4a716446655440000";
/// let uuid: Uuid = uuid!(uuid_str);
/// ```
/// Provides the following compilation error:
/// ```txt
/// error: expected string literal
/// |
/// | let uuid: Uuid = uuid!(uuid_str);
/// | ^^^^^^^^
/// ```
///
/// [uuid::Uuid]: https://docs.rs/uuid/*/uuid/struct.Uuid.html
#[macro_export]
macro_rules! uuid {
($uuid:tt) => {{
$crate::Uuid::from_bytes($crate::uuid_macros::parse_lit!($uuid))
}};
}

0 comments on commit fe7bbc7

Please sign in to comment.