1
#![allow(clippy::default_trait_access)]
2

            
3
use darling::{FromDeriveInput, ToTokens};
4
use proc_macro2::TokenStream;
5
use proc_macro_error::abort;
6
use quote::quote;
7
use syn::{parse::Parse, punctuated::Punctuated};
8

            
9
use crate::{actionable, Error};
10

            
11
8
#[derive(Debug, FromDeriveInput)]
12
#[darling(supports(any))]
13
struct Dispatcher {
14
    ident: syn::Ident,
15
    generics: syn::Generics,
16
    #[darling(skip)]
17
    args: Option<Args>,
18
}
19

            
20
4
#[derive(Debug, Default)]
21
struct Args {
22
    inputs: Vec<syn::Path>,
23
    actionable: Option<syn::Path>,
24
    asynchronous: bool,
25
}
26

            
27
impl Parse for Args {
28
4
    fn parse(input: &'_ syn::parse::ParseBuffer<'_>) -> syn::Result<Self> {
29
4
        let content;
30
4
        let _ = syn::parenthesized!(content in input);
31
4
        let content: Punctuated<Arg, syn::Token![,]> = content.parse_terminated(Arg::parse)?;
32
4
        let mut args = Self::default();
33
10
        for arg in content {
34
6
            match arg {
35
2
                Arg::Actionable(actionable) => {
36
2
                    args.actionable = Some(actionable);
37
2
                }
38
4
                Arg::Input(path) => {
39
4
                    args.inputs.push(path);
40
4
                }
41
            }
42
        }
43

            
44
4
        Ok(args)
45
4
    }
46
}
47

            
48
impl ToTokens for Dispatcher {
49
4
    fn to_tokens(&self, tokens: &mut TokenStream) {
50
4
        let type_name = &self.ident;
51
4

            
52
4
        let args = self.args.as_ref().unwrap();
53
4

            
54
4
        let actionable = actionable(args.actionable.clone(), type_name.span());
55

            
56
4
        let (dispatcher_trait, async_keyword, await_suffix, async_trait_attribute) =
57
4
            if args.asynchronous {
58
3
                (
59
3
                    quote!(AsyncDispatcher),
60
3
                    quote!(async),
61
3
                    quote!(.await),
62
3
                    quote!(#[#actionable::async_trait]),
63
3
                )
64
            } else {
65
1
                (quote!(Dispatcher), quote!(), quote!(), quote!())
66
            };
67

            
68
4
        let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
69

            
70
8
        for enum_type in &args.inputs {
71
4
            let generated_dispatcher_name = syn::Ident::new(
72
4
                &format!("{}Dispatcher", enum_type.segments.last().unwrap().ident),
73
4
                type_name.span(),
74
4
            );
75
4

            
76
4
            tokens.extend(quote! {
77
4
                #async_trait_attribute
78
4
                impl#impl_generics #actionable::#dispatcher_trait<#enum_type> for #type_name#type_generics #where_clause {
79
4
                    type Result = Result<<Self as #generated_dispatcher_name>::Output,<Self as #generated_dispatcher_name>::Error>;
80
4

            
81
4
                    #async_keyword fn dispatch(&self, permissions: &#actionable::Permissions, request: #enum_type) -> Self::Result {
82
4
                        #generated_dispatcher_name::dispatch_to_handlers(self, permissions, request)#await_suffix
83
4
                    }
84
4
                }
85
4
            });
86
4
        }
87
4
    }
88
}
89

            
90
#[allow(clippy::redundant_pub_crate)] // Error is a private type
91
4
pub(crate) fn derive(input: &syn::DeriveInput, asynchronous: bool) -> Result<TokenStream, Error> {
92
4
    let mut dispatcher = Dispatcher::from_derive_input(input)?;
93
4
    let attr = match input
94
4
        .attrs
95
4
        .iter()
96
6
        .find(|attr| attr.path.segments.first().unwrap().ident == "dispatcher")
97
    {
98
4
        Some(attr) => attr,
99
        None => abort!(input.ident, "missing `dispatcher` attribute"),
100
    };
101
4
    let mut args: Args = syn::parse2(attr.tokens.clone())?;
102
4
    args.asynchronous = asynchronous;
103
4
    dispatcher.args = Some(args);
104
4
    Ok(dispatcher.into_token_stream())
105
4
}
106

            
107
enum Arg {
108
    Actionable(syn::Path),
109
    Input(syn::Path),
110
}
111

            
112
impl Parse for Arg {
113
6
    fn parse(input: &'_ syn::parse::ParseBuffer<'_>) -> syn::Result<Self> {
114
6
        let ident: syn::Ident = input.parse()?;
115
6
        match ident.to_string().as_str() {
116
6
            "actionable" => {
117
2
                let _: syn::Token![=] = input.parse()?;
118
2
                Ok(Self::Actionable(input.parse()?))
119
            }
120
4
            "input" => {
121
4
                let _: syn::Token![=] = input.parse()?;
122
4
                Ok(Self::Input(input.parse()?))
123
            }
124
            _ => abort!(ident, "unknown parameter"),
125
        }
126
6
    }
127
}