dose_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::parse_macro_input;
4
5const SINGLETON_DEFAULT: bool = false;
6
7#[proc_macro_attribute]
8pub fn provider(args: TokenStream, input: TokenStream) -> TokenStream {
9    let args = parse_macro_input!(args as syn::AttributeArgs);
10    let input = parse_macro_input!(input as syn::ItemFn);
11    provider_impl(&args, &input)
12}
13
14fn provider_impl(args: &syn::AttributeArgs, input: &syn::ItemFn) -> TokenStream {
15    let input_fn_name = input.sig.ident.clone();
16    let config_type_name = get_config_type_name(input);
17    let (output_type_name, output_type_params) = get_output_type_name(input);
18    let is_singleton = get_singleton_tag(args);
19
20    let log_creating = format!(
21        "Type '{}' is not global, creating new instance.",
22        output_type_name.to_string()
23    );
24    let output_type_name_as_str = output_type_name.to_string();
25
26    let fn_call = match is_singleton {
27        false => quote! {
28            debug!(#log_creating);
29            #input_fn_name(self)
30        },
31        true => quote! {
32            self.resolve_singleton(|context| {
33                #input_fn_name(context)
34            }, #output_type_name_as_str)
35
36        },
37    };
38
39    let gen = quote! {
40        impl crate::dose_private::Injector<#output_type_name #output_type_params> for dose::Context<#config_type_name> {
41            fn get(&mut self) -> #output_type_name #output_type_params {
42                #fn_call
43            }
44        }
45        #input
46    };
47    gen.into()
48}
49
50fn get_singleton_tag(args: &syn::AttributeArgs) -> bool {
51    let singleton_tag = match args.first() {
52        Some(val) => val,
53        None => return SINGLETON_DEFAULT,
54    };
55    let singleton_tag = match singleton_tag {
56        syn::NestedMeta::Meta(meta) => meta,
57        _ => panic!("Resolver trait not provided"),
58    };
59    let tag_name = singleton_tag.path().segments.first().unwrap().ident.clone();
60    if tag_name.to_string() != "singleton" {
61        return false;
62    }
63    let tag_value = match singleton_tag {
64        syn::Meta::NameValue(val) => val,
65        _ => return false,
66    };
67    let tag_value = match &tag_value.lit {
68        syn::Lit::Bool(val) => val,
69        _ => panic!("Should be a bool"),
70    };
71
72    tag_value.value
73}
74
75fn get_output_type_name(input: &syn::ItemFn) -> (syn::Ident, syn::PathArguments) {
76    let output_type = match &input.sig.output {
77        syn::ReturnType::Type(_, b) => b.as_ref().clone(),
78        _ => panic!("No output to the function"),
79    };
80    let output_type = match output_type {
81        syn::Type::Path(path) => path,
82        _ => panic!("Unsupported type"),
83    };
84    let output_type = output_type.path.segments.first().unwrap();
85
86    (output_type.ident.clone(), output_type.arguments.clone())
87}
88
89fn get_config_type_name(input: &syn::ItemFn) -> syn::Ident {
90    let config_type = match input.sig.inputs.first() {
91        Some(ty) => ty,
92        None => panic!("Function need a first argument"),
93    };
94    let config_type = match config_type {
95        syn::FnArg::Typed(ty) => ty,
96        _ => panic!("Not typed"),
97    };
98    let config_type = match &*config_type.ty {
99        syn::Type::Reference(r) => r,
100        _ => panic!("Argument should be a reference to the resolver"),
101    };
102    let config_type = match &*config_type.elem {
103        syn::Type::Path(path) => path,
104        _ => panic!("Argument should be a reference to the resolver"),
105    };
106    let config_type = config_type.path.segments.first().unwrap();
107    let config_type = match &config_type.arguments {
108        syn::PathArguments::AngleBracketed(br) => br,
109        _ => panic!("Argument should be a reference to the resolver"),
110    };
111    let config_type = config_type.args.first().unwrap();
112    let config_type = match config_type {
113        syn::GenericArgument::Type(ty) => ty,
114        _ => panic!("Argument should be a reference to the resolver"),
115    };
116    let config_type = match &*config_type {
117        syn::Type::Path(path) => path,
118        _ => panic!("Argument should be a reference to the resolver"),
119    };
120    let config_type = config_type.path.segments.first().unwrap();
121    let config_type = config_type.ident.clone();
122
123    config_type
124}