compiled_uuid/
lib.rs

1//! [![github-img]][github-url] [![crates-img]][crates-url] [![docs-img]][docs-url]
2//! 
3//! [github-url]: https://github.com/QnnOkabayashi/compiled-uuid
4//! [crates-url]: https://crates.io/crates/compiled-uuid
5//! [docs-url]: https://docs.rs/compiled-uuid/*/compiled_uuid
6//! [github-img]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
7//! [crates-img]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
8//! [docs-img]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
9//!
10//! Anywhere you're building [`Uuid`][uuid::Uuid]s from a string literal, you should use [`uuid`][compiled_uuid::uuid].
11//! 
12//! ## Overview
13//! `compiled_uuid` exposes one macro called [`uuid`][compiled_uuid::uuid], which parses [`Uuid`][uuid::Uuid]s at compile time.
14//!
15//! When you write this:
16//! ```rust
17//! let id: Uuid = uuid!("F9168C5E-CEB2-4FAA-B6BF-329BF39FA1E4");
18//! ```
19//! It expands to:
20//! ```rust
21//! let id: Uuid = ::uuid::Uuid::from_bytes([
22//!     249u8, 22u8, 140u8, 94u8, 206u8, 178u8, 79u8, 170u8, 182u8, 191u8, 50u8, 155u8, 243u8,
23//!     159u8, 161u8, 228u8,
24//! ]);
25//! ```
26//! 
27//! [uuid::Uuid]: https://docs.rs/uuid/*/uuid/struct.Uuid.html
28//! [compiled_uuid::uuid]: https://docs.rs/compiled-uuid/*/compiled_uuid/macro.uuid.html#
29
30use proc_macro::TokenStream;
31use proc_macro2::TokenStream as TokenStream2;
32use quote::{quote, quote_spanned};
33use syn::spanned::Spanned;
34use thiserror::Error;
35use uuid::Uuid;
36
37/// Parse [`Uuid`][uuid::Uuid]s from string literals at compile time.
38/// ## Usage
39/// This macro transforms the string literal representation of a [`Uuid`][uuid::Uuid] into the bytes representation,
40/// raising a compilation error if it cannot properly be parsed.
41/// 
42/// ## Examples
43/// Setting a global constant:
44/// ```
45/// # use uuid::Uuid;
46/// # use compiled_uuid::uuid;
47/// pub const SCHEMA_ATTR_CLASS: Uuid = uuid!("00000000-0000-0000-0000-ffff00000000");
48/// pub const SCHEMA_ATTR_UUID: Uuid = uuid!("00000000-0000-0000-0000-ffff00000001");
49/// pub const SCHEMA_ATTR_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000002");
50/// ```
51/// Defining a local variable:
52/// ```
53/// # use uuid::Uuid;
54/// # use compiled_uuid::uuid;
55/// let uuid: Uuid = uuid!("urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4");
56/// ```
57/// ## Compilation Failures
58/// Invalid UUIDs are rejected:
59/// ```ignore
60/// # use uuid::Uuid;
61/// # use compiled_uuid::uuid;
62/// let uuid: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4");
63/// ```
64/// Provides the following compilation error:
65/// ```txt
66/// error: invalid character: expected an optional prefix of `urn:uuid:` followed by 0123456789abcdefABCDEF-, found Z at 9
67///     |
68///     |     let id: Uuid = uuid!("F9168C5E-ZEB2-4FAA-B6BF-329BF39FA1E4");
69///     |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
70/// ```
71/// Tokens that aren't string literals are also rejected:
72/// ```ignore
73/// # use uuid::Uuid;
74/// # use compiled_uuid::uuid;
75/// let uuid_str: &str = "550e8400e29b41d4a716446655440000";
76/// let uuid: Uuid = uuid!(uuid_str);
77/// ```
78/// Provides the following compilation error:
79/// ```txt
80/// error: expected string literal
81///   |
82///   |     let uuid: Uuid = uuid!(uuid_str);
83///   |                            ^^^^^^^^
84/// ```
85/// 
86/// [uuid::Uuid]: https://docs.rs/uuid/0.8.2/uuid/struct.Uuid.html
87#[proc_macro]
88pub fn uuid(input: TokenStream) -> TokenStream {
89    build_uuid(input.clone()).unwrap_or_else(|e| {
90        let msg = format!("{}", e);
91        TokenStream::from(quote_spanned! {
92            TokenStream2::from(input).span() =>
93            compile_error!(#msg)
94        })
95    })
96}
97
98#[derive(Debug, Error)]
99enum Error {
100    #[error("expected string literal")]
101    NonLiteral(#[from] syn::Error),
102    #[error("expected string literal")]
103    NonStringLiteral,
104    #[error("{0}")]
105    UuidParse(#[from] uuid::Error),
106}
107
108fn build_uuid(input: TokenStream) -> Result<TokenStream, Error> {
109    let uuid = match syn::parse::<syn::Lit>(input)? {
110        syn::Lit::Str(ref literal) => literal.value(),
111        _ => return Err(Error::NonStringLiteral),
112    };
113
114    let uuid = Uuid::parse_str(&uuid)?;
115
116    let tokens = uuid
117        .as_bytes()
118        .iter()
119        .map(|byte| {
120            quote! {
121                #byte,
122            }
123        })
124        .collect::<TokenStream2>();
125
126    Ok(quote! {::uuid::Uuid::from_bytes([#tokens])}.into())
127}