1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;

/// Derive macro for the `TypeUlid` trait.
///
/// # Example
///
/// ```ignore
/// #[derive(TypeUlid)]
/// #[ulid = "01GNDEY1ZEC7BGREZNG2JNTPRP"]
/// struct MyStruct;
/// ```
#[proc_macro_derive(TypeUlid, attributes(ulid))]
pub fn type_ulid(input: TokenStream) -> TokenStream {
    let input = syn::parse(input).unwrap();

    impl_type_ulid(&input).into()
}

fn impl_type_ulid(input: &syn::DeriveInput) -> TokenStream2 {
    let item_ident = &input.ident;

    // Check for `#[ulid = "ulid"]` attribute
    let mut ulid = None;
    for attr in &input.attrs {
        let Ok(syn::Meta::NameValue(name_value)) = attr.parse_meta() else {
            continue;
        };

        if name_value
            .path
            .get_ident()
            .map(|i| i != "ulid")
            .unwrap_or(true)
        {
            continue;
        }

        let syn::Lit::Str(lit_str) = name_value.lit else {
            continue;
        };

        match ulid::Ulid::from_string(&lit_str.value()) {
            Ok(id) => ulid = Some(id),
            Err(e) => {
                let msg = e.to_string();
                return quote_spanned! { attr.span() =>
                    compile_error!(concat!("Could not parse ULID: ", #msg));
                };
            }
        }
    }

    let Some(ulid) = ulid else {
        return quote! {
            compile_error!("You must specify a `ulid` attribute");
        };
    };

    // Add impl block
    let id = ulid.0;
    quote! {
        impl ::type_ulid::TypeUlid for #item_ident {
            const ULID: ::type_ulid::Ulid = ::type_ulid::Ulid(#id);
        }
    }
}