sourcerer_derive/
lib.rs

1//! A derive macro for the `Event` trait in the `sourcerer` crate.
2use proc_macro::TokenStream;
3use proc_macro2::Span;
4use quote::quote;
5use syn::{
6    Data, DeriveInput, Fields, Lit, LitStr, MetaNameValue, Token, parse_macro_input,
7    punctuated::Punctuated,
8};
9
10/// Derives the `Event` trait for an enum.
11///
12/// This macro automatically implements the `event_type` method, which returns
13/// a string slice representing the variant's name.
14#[proc_macro_derive(Event, attributes(event))]
15pub fn event_derive(input: TokenStream) -> TokenStream {
16    let input = parse_macro_input!(input as DeriveInput);
17    let name = &input.ident;
18
19    // Helper to parse meta list for keys version, source
20    fn extract_meta(
21        list: &Punctuated<MetaNameValue, Token![,]>,
22        version: &mut Option<u16>,
23        source: &mut Option<String>,
24    ) {
25        for nv in list {
26            let ident = nv.path.get_ident().map(|i| i.to_string());
27            if let Some(key) = ident {
28                match key.as_str() {
29                    "version" => {
30                        if let syn::Expr::Lit(expr_lit) = &nv.value {
31                            if let Lit::Int(li) = &expr_lit.lit {
32                                *version = Some(li.base10_parse::<u16>().expect("invalid int"));
33                            }
34                        }
35                    }
36                    "source" => {
37                        if let syn::Expr::Lit(expr_lit) = &nv.value {
38                            if let Lit::Str(ls) = &expr_lit.lit {
39                                *source = Some(ls.value());
40                            }
41                        }
42                    }
43                    _ => {}
44                }
45            }
46        }
47    }
48
49    // Enum-level defaults
50    let mut enum_version: Option<u16> = None;
51    let mut enum_source: Option<String> = None;
52
53    for attr in &input.attrs {
54        if attr.path().is_ident("event") {
55            let parser = Punctuated::<MetaNameValue, Token![,]>::parse_terminated;
56            let list = attr
57                .parse_args_with(parser)
58                .expect("invalid event attribute");
59            extract_meta(&list, &mut enum_version, &mut enum_source);
60        }
61    }
62
63    let default_version = enum_version.unwrap_or(1);
64    let default_source = enum_source.unwrap_or_else(|| "urn:sourcerer:event".to_string());
65
66    let variants = match &input.data {
67        Data::Enum(data) => &data.variants,
68        _ => panic!("Event derive macro can only be used on enums"),
69    };
70
71    // Build arms for event_type, version, source
72    let mut type_arms = Vec::new();
73    let mut version_arms = Vec::new();
74    let mut source_arms = Vec::new();
75
76    for variant in variants {
77        let ident = &variant.ident;
78        let fields_tokens = match &variant.fields {
79            Fields::Named(_) => quote! { { .. } },
80            Fields::Unnamed(_) => quote! { (..) },
81            Fields::Unit => quote! {},
82        };
83
84        // Variant attribute overrides
85        let mut var_version = None;
86        let mut var_source = None;
87        for attr in &variant.attrs {
88            if attr.path().is_ident("event") {
89                let parser = Punctuated::<MetaNameValue, Token![,]>::parse_terminated;
90                let list = attr
91                    .parse_args_with(parser)
92                    .expect("invalid event attribute");
93                extract_meta(&list, &mut var_version, &mut var_source);
94            }
95        }
96
97        let ver_val = var_version.unwrap_or(default_version);
98        let src_val = var_source.unwrap_or_else(|| default_source.clone());
99        let src_lit = syn::LitStr::new(&src_val, Span::call_site());
100
101        type_arms.push(quote! { #name::#ident #fields_tokens => stringify!(#ident) });
102        version_arms.push(quote! { #name::#ident #fields_tokens => #ver_val });
103        source_arms.push(quote! { #name::#ident #fields_tokens => #src_lit });
104    }
105
106    let event_type_arms = variants.iter().map(|variant| {
107        let variant_name = &variant.ident;
108        let fields = match &variant.fields {
109            Fields::Named(_) => quote! { { .. } },
110            Fields::Unnamed(_) => quote! { (..) },
111            Fields::Unit => quote! {},
112        };
113        quote! {
114            #name::#variant_name #fields => stringify!(#variant_name)
115        }
116    });
117
118    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
119
120    let expanded = quote! {
121        impl #impl_generics sourcerer::Event for #name #ty_generics #where_clause {
122            fn event_type(&self) -> &'static str {
123                match self {
124                    #(#type_arms),*
125                }
126            }
127
128            fn event_version(&self) -> u16 {
129                match self {
130                    #(#version_arms),*
131                }
132            }
133
134            fn event_source(&self) -> &'static str {
135                match self {
136                    #(#source_arms),*
137                }
138            }
139        }
140    };
141
142    TokenStream::from(expanded)
143}