Skip to main content

xdi_macro/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Ident, Span};
3use quote::{ToTokens, quote};
4use syn::punctuated::Punctuated;
5use syn::token::Comma;
6use syn::{Expr, ExprArray, Lit, PatLit};
7use syn::{ItemFn, parse_macro_input};
8
9#[proc_macro_attribute]
10pub fn register_constructor(attr: TokenStream, item: TokenStream) -> TokenStream {
11    // Парсим аргументы как key = value через запятую
12    let args =
13        parse_macro_input!(attr with Punctuated::<syn::MetaNameValue, Comma>::parse_terminated);
14
15    let args = args.into_iter().collect::<Vec<syn::MetaNameValue>>();
16
17    let inject_scope = args
18        .iter()
19        .find(|x| x.path.get_ident().is_some_and(|x| x.to_string() == "scope"))
20        .cloned();
21
22    let maps = args
23        .iter()
24        .find(|x| x.path.get_ident().is_some_and(|x| x.to_string() == "map"))
25        .cloned();
26
27    let maps = maps
28        .as_ref()
29        .and_then(|x| {
30            if let Expr::Array(ExprArray { elems, .. }) = &x.value {
31                Some(elems.iter().collect::<Vec<_>>())
32            } else {
33                None
34            }
35        })
36        .unwrap_or_default();
37
38    let maps_quote = maps
39        .iter()
40        .map(|map| quote! { builder.map_as_trait::<dyn #map>(); })
41        .collect::<Vec<_>>();
42
43    let input_fn = parse_macro_input!(item as ItemFn);
44    let fn_name = &input_fn.sig.ident;
45
46    let crate_name = proc_macro_crate::crate_name("xdi").expect("Failed to get crate name for xdi");
47
48    let crate_name = match crate_name {
49        proc_macro_crate::FoundCrate::Name(name) => Ident::new(&name, Span::call_site()),
50        proc_macro_crate::FoundCrate::Itself => Ident::new("crate", Span::call_site()),
51    };
52
53    let scope = inject_scope.as_ref().and_then(|x| {
54        if let Expr::Lit(PatLit {
55            lit: Lit::Str(val), ..
56        }) = &x.value
57        {
58            Some(val.value())
59        } else {
60            None
61        }
62    });
63
64    if let Some(inject_scope) = inject_scope
65        && scope.is_none()
66    {
67        panic!(
68            r#"Invalid scope value in register_constructor: {:?}, expected: "singleton", "transient", "task_local", "thread_local""#,
69            inject_scope.value.to_token_stream()
70        );
71    }
72
73    if scope.as_ref().is_some_and(|x| x == "singleton") {
74        let expanded = quote! {
75            #input_fn
76
77            inventory::submit! {
78                #crate_name::Registration {
79                    constructor: &|builder| {
80                        let builder = builder.singletone(#fn_name);
81
82                        #(#maps_quote)*
83                    }
84                }
85            }
86        };
87
88        return expanded.into();
89    }
90
91    if scope.as_ref().is_some_and(|x| x == "thread_local") {
92        let expanded = quote! {
93            #input_fn
94
95            inventory::submit! {
96                #crate_name::Registration {
97                    constructor: &|builder| {
98                        let builder = builder.thread_local(#fn_name);
99
100                        #(#maps_quote)*
101                    }
102                }
103            }
104        };
105
106        return expanded.into();
107    }
108
109    if scope.as_ref().is_some_and(|x| x == "task_local") {
110        let expanded = quote! {
111            #input_fn
112
113            inventory::submit! {
114                #crate_name::Registration {
115                    constructor: &|builder| {
116                        let builder = builder.task_local(#fn_name);
117
118                        #(#maps_quote)*
119                    }
120                }
121            }
122        };
123
124        return expanded.into();
125    }
126
127    if scope.as_ref().is_none_or(|x| x == "transient") {
128        let expanded = quote! {
129            #input_fn
130
131            inventory::submit! {
132                #crate_name::Registration {
133                    constructor: &|builder| {
134                        let builder = builder.transient(#fn_name);
135
136                        #(#maps_quote)*
137                    }
138                }
139            }
140        };
141
142        return expanded.into();
143    }
144
145    panic!("Unsupported inject scope: {:?}", scope);
146}