idemio_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Field, Type, parse_macro_input};
4
5#[proc_macro_derive(ConfigurableHandler)]
6pub fn derive_init_function(input: TokenStream) -> TokenStream {
7    use syn::{Data, DeriveInput, Fields, parse_macro_input};
8
9    // Parse the input tokens into a syntax tree
10    let input = parse_macro_input!(input as DeriveInput);
11
12    // Get the struct name
13    let struct_name = &input.ident;
14
15    // Validate the struct has exactly one field
16    let field_count = match &input.data {
17        Data::Struct(data_struct) => match &data_struct.fields {
18            Fields::Named(fields_named) => fields_named.named.len(),
19            Fields::Unnamed(fields_unnamed) => fields_unnamed.unnamed.len(),
20            Fields::Unit => 0, // Unit struct has no fields
21        },
22        _ => {
23            return TokenStream::from(
24                syn::Error::new_spanned(input, "ConfigurableHandler can only be derived for structs.")
25                    .to_compile_error(),
26            );
27        }
28    };
29
30    // Ensure the struct has exactly 1 field
31    if field_count != 1 {
32        return TokenStream::from(
33            syn::Error::new_spanned(
34                input,
35                "ConfigurableHandler can only be derived for structs with exactly one field.",
36            )
37            .to_compile_error(),
38        );
39    }
40
41    let mut inner_type = None;
42
43    if let Data::Struct(data_struct) = input.data {
44        if let Fields::Named(fields_named) = data_struct.fields {
45            for field in fields_named.named {
46                inner_type = find_config_inner_type(field);
47            }
48        }
49    }
50    let inner_type = inner_type.expect("The struct must contain a field with type Config<X>.");
51
52    let generated = quote! {
53        impl #struct_name {
54            pub fn init_handler(config: Config<#inner_type>) -> #struct_name {
55                #struct_name {
56                    config
57                }
58            }
59
60            pub fn id() -> &'static str {
61                stringify!(#struct_name)
62            }
63
64            pub fn config_file_name() -> &'static str {
65                stringify!(#struct_name.json)
66            }
67
68            pub fn config(&self) -> &Config<#inner_type> {
69                &self.config
70            }
71
72            pub fn reload_config(&mut self, config: Config<#inner_type>) {
73                self.config = config;
74            }
75        }
76    };
77    TokenStream::from(generated)
78}
79
80fn find_config_inner_type(field: Field) -> Option<Type> {
81    use syn::{PathArguments, Type, TypePath};
82
83    if let Type::Path(TypePath { path, .. }) = &field.ty {
84        // Get the last segment of the field.
85        // i.e. idemio-config::config::Config<X>, or Config<X>
86        if let Some(segment) = path.segments.last() {
87            // Make sure the outer type is 'Config' and grab the inner type argument
88            if segment.ident == "Config" {
89                if let PathArguments::AngleBracketed(type_argument) = &segment.arguments {
90                    if let Some(syn::GenericArgument::Type(inner)) = type_argument.args.first() {
91                        return Some(inner.clone());
92                    }
93                }
94            }
95        }
96    }
97    None
98}