type_ulid_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{quote, quote_spanned};
4use syn::spanned::Spanned;
5
6/// Derive macro for the `TypeUlid` trait.
7///
8/// # Example
9///
10/// ```ignore
11/// #[derive(TypeUlid)]
12/// #[ulid = "01GNDEY1ZEC7BGREZNG2JNTPRP"]
13/// struct MyStruct;
14/// ```
15#[proc_macro_derive(TypeUlid, attributes(ulid))]
16pub fn type_ulid(input: TokenStream) -> TokenStream {
17    let input = syn::parse(input).unwrap();
18
19    impl_type_ulid(&input).into()
20}
21
22fn impl_type_ulid(input: &syn::DeriveInput) -> TokenStream2 {
23    let item_ident = &input.ident;
24
25    // Check for `#[ulid = "ulid"]` attribute
26    let mut ulid = None;
27    for attr in &input.attrs {
28        let Ok(syn::Meta::NameValue(name_value)) = attr.parse_meta() else {
29            continue;
30        };
31
32        if name_value
33            .path
34            .get_ident()
35            .map(|i| i != "ulid")
36            .unwrap_or(true)
37        {
38            continue;
39        }
40
41        let syn::Lit::Str(lit_str) = name_value.lit else {
42            continue;
43        };
44
45        match ulid::Ulid::from_string(&lit_str.value()) {
46            Ok(id) => ulid = Some(id),
47            Err(e) => {
48                let msg = e.to_string();
49                return quote_spanned! { attr.span() =>
50                    compile_error!(concat!("Could not parse ULID: ", #msg));
51                };
52            }
53        }
54    }
55
56    let Some(ulid) = ulid else {
57        return quote! {
58            compile_error!("You must specify a `ulid` attribute");
59        };
60    };
61
62    // Add impl block
63    let id = ulid.0;
64    quote! {
65        impl ::type_ulid::TypeUlid for #item_ident {
66            const ULID: ::type_ulid::Ulid = ::type_ulid::Ulid(#id);
67        }
68    }
69}