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

            
3
use darling::{ast, FromDeriveInput, FromField, FromMeta, FromVariant, ToTokens};
4
use proc_macro2::TokenStream;
5
use proc_macro_error::abort;
6
use quote::{quote, quote_spanned};
7

            
8
use crate::{actionable, ActionableArgs};
9

            
10
12
#[derive(Debug, FromDeriveInput)]
11
#[darling(supports(enum_any))]
12
struct Actionable {
13
    ident: syn::Ident,
14
    vis: syn::Visibility,
15
    data: ast::Data<Variant, ()>,
16

            
17
    /// Overrides the crate name for `actionable` references.
18
    #[darling(skip)]
19
    actionable: Option<ActionableArgs>,
20
}
21

            
22
17
#[derive(Debug, FromMeta)]
23
enum Protection {
24
    None,
25
    Simple,
26
    Custom,
27
}
28

            
29
impl Default for Protection {
30
    fn default() -> Self {
31
        Self::None
32
    }
33
}
34

            
35
68
#[derive(Debug, FromVariant)]
36
#[darling(attributes(actionable))]
37
struct Variant {
38
    ident: syn::Ident,
39
    fields: ast::Fields<Field>,
40

            
41
    protection: Protection,
42
    #[darling(default)]
43
    subaction: bool,
44
}
45

            
46
struct VariantResult {
47
    handler: VariantHandler,
48
    match_case: TokenStream,
49
}
50

            
51
enum VariantHandler {
52
    Subaction,
53
    Handler {
54
        handler: TokenStream,
55
        name: syn::Ident,
56
    },
57
}
58

            
59
1
#[derive(Default)]
60
struct Handler {
61
    tokens: TokenStream,
62
    parameters: Vec<syn::Ident>,
63
}
64

            
65
struct Context<'a> {
66
    enum_name: &'a syn::Ident,
67
    generated_dispatcher_name: &'a syn::Ident,
68
    pub_tokens: &'a TokenStream,
69
    actionable: &'a syn::Path,
70
    async_keyword: &'a TokenStream,
71
    await_suffix: &'a TokenStream,
72
    async_trait_attribute: &'a TokenStream,
73
}
74

            
75
impl Variant {
76
17
    pub fn generate_code(&self, context: &Context<'_>) -> VariantResult {
77
17
        let variant_name = &self.ident;
78
17
        let handler_name =
79
17
            syn::Ident::new(&format!("{}Handler", variant_name), variant_name.span());
80
17

            
81
17
        let mut method_parameters = Vec::new();
82
17
        let mut byref_method_parameters = Vec::new();
83
17
        let mut enum_parameters = Vec::new();
84
17
        let mut is_struct_style = false;
85
17
        let byref_lifetime = if matches!(self.protection, Protection::Simple) {
86
5
            quote!('a)
87
        } else {
88
12
            TokenStream::default()
89
        };
90

            
91
17
        for (index, field) in self.fields.iter().enumerate() {
92
11
            let arg_name = field.ident.as_ref().map_or_else(
93
11
                || syn::Ident::new(&format!("arg{}", index), variant_name.span()),
94
11
                |ident| {
95
5
                    is_struct_style = true;
96
5
                    ident.clone()
97
11
                },
98
11
            );
99
11
            let arg_type = &field.ty;
100
11
            method_parameters.push(quote!(#arg_name: #arg_type));
101
11
            byref_method_parameters.push(quote!(#arg_name: &#byref_lifetime #arg_type));
102
11
            enum_parameters.push(arg_name);
103
11
        }
104

            
105
17
        let handler = if self.subaction {
106
1
            if self.fields.len() != 1 {
107
                abort!(self.ident, "subactions should only have one field")
108
1
            }
109
1

            
110
1
            Handler::default()
111
        } else {
112
16
            self.generate_handler(
113
16
                &handler_name,
114
16
                &enum_parameters,
115
16
                &method_parameters,
116
16
                &byref_method_parameters,
117
16
                context,
118
16
            )
119
        };
120

            
121
17
        let match_case = self.generate_match_case(
122
17
            is_struct_style,
123
17
            &handler_name,
124
17
            &enum_parameters,
125
17
            &handler.parameters,
126
17
            context,
127
17
        );
128

            
129
17
        let handler = if self.subaction {
130
1
            VariantHandler::Subaction
131
        } else {
132
16
            VariantHandler::Handler {
133
16
                handler: handler.tokens,
134
16
                name: handler_name,
135
16
            }
136
        };
137

            
138
17
        VariantResult {
139
17
            handler,
140
17
            match_case,
141
17
        }
142
17
    }
143

            
144
    #[allow(clippy::cognitive_complexity)]
145
    // The complexity is because of the multiple big quote! macros, but I don't see a way to make
146
    // this much easier to read
147
    #[allow(clippy::too_many_arguments)] // TODO maybe refactor?
148
16
    fn generate_handler(
149
16
        &self,
150
16
        handler_name: &syn::Ident,
151
16
        enum_parameters: &[syn::Ident],
152
16
        method_parameters: &[TokenStream],
153
16
        byref_method_parameters: &[TokenStream],
154
16
        context: &Context<'_>,
155
16
    ) -> Handler {
156
16
        let variant_name = &self.ident;
157
16

            
158
16
        let mut handle_parameters = enum_parameters.to_vec();
159
16
        handle_parameters.insert(0, syn::Ident::new("self", variant_name.span()));
160
16
        handle_parameters.insert(1, syn::Ident::new("permissions", variant_name.span()));
161
16

            
162
16
        let generated_dispatcher_name = context.generated_dispatcher_name;
163
16
        let self_as_dispatcher = quote! {<Self as #generated_dispatcher_name>};
164
16
        let result_type = quote!(Result<
165
16
            #self_as_dispatcher::Output,
166
16
            #self_as_dispatcher::Error
167
16
        >);
168
16
        let async_keyword = context.async_keyword;
169
16
        let actionable = context.actionable;
170
16
        let await_suffix = context.await_suffix;
171
16
        let implementation = match self.protection {
172
6
            Protection::None => quote! {
173
6
                #[allow(clippy::too_many_arguments)]
174
6
                #async_keyword fn handle(
175
6
                    &self,
176
6
                    permissions: &#actionable::Permissions,
177
6
                    #(#method_parameters),*
178
6
                ) -> #result_type;
179
6
            },
180
            Protection::Simple => {
181
5
                quote! {
182
5
                    #[allow(clippy::ptr_arg, clippy::too_many_arguments)]
183
5
                    #async_keyword fn resource_name<'a>(&'a self,#(#byref_method_parameters),*) -> Result<#actionable::ResourceName<'a>, #self_as_dispatcher::Error>;
184
5
                    type Action: #actionable::Action;
185
5
                    fn action() -> Self::Action;
186
5

            
187
5
                    #[allow(clippy::too_many_arguments)]
188
5
                    #async_keyword fn handle(
189
5
                        &self,
190
5
                        permissions: &#actionable::Permissions,
191
5
                        #(#method_parameters),*
192
5
                    ) -> #result_type {
193
5
                        let resource = self.resource_name(#(&#enum_parameters),*)#await_suffix?;
194
5
                        let action = Self::action();
195
5
                        permissions.check(&resource, &action)?;
196
5
                        self.handle_protected(permissions, #(#enum_parameters),*)#await_suffix
197
5
                    }
198
5

            
199
5
                    #[allow(clippy::too_many_arguments)]
200
5
                    #async_keyword fn handle_protected(
201
5
                        &self,
202
5
                        permissions: &#actionable::Permissions,
203
5
                        #(#method_parameters),*
204
5
                    ) -> #result_type;
205
5
                }
206
            }
207
            Protection::Custom => {
208
5
                quote! {
209
5
                    #[allow(clippy::ptr_arg, clippy::too_many_arguments)]
210
5
                    #async_keyword fn verify_permissions(&self, permissions: &#actionable::Permissions, #(#byref_method_parameters),*) -> Result<(), #self_as_dispatcher::Error>;
211
5

            
212
5
                    #[allow(clippy::too_many_arguments)]
213
5
                    #async_keyword fn handle(
214
5
                        &self,
215
5
                        permissions: &#actionable::Permissions,
216
5
                        #(#method_parameters),*
217
5
                    ) -> #result_type {
218
5
                        self.verify_permissions(permissions, #(&#enum_parameters),*)#await_suffix?;
219
5
                        self.handle_protected(permissions, #(#enum_parameters),*)#await_suffix
220
5
                    }
221
5

            
222
5
                    #[allow(clippy::too_many_arguments)]
223
5
                    #async_keyword fn handle_protected(
224
5
                        &self,
225
5
                        permissions: &#actionable::Permissions,
226
5
                        #(#method_parameters),*
227
5
                    ) -> #result_type;
228
5
                }
229
            }
230
        };
231

            
232
16
        let async_trait_attribute = context.async_trait_attribute;
233
16
        let pub_tokens = context.pub_tokens;
234
16
        Handler {
235
16
            parameters: handle_parameters,
236
16
            tokens: quote_spanned! {
237
16
                variant_name.span() =>
238
16
                    #async_trait_attribute
239
16
                    #[doc(hidden)]
240
16
                    #pub_tokens trait #handler_name: #generated_dispatcher_name  {
241
16
                        #implementation
242
16
                    }
243
16
            },
244
16
        }
245
16
    }
246

            
247
17
    fn generate_match_case(
248
17
        &self,
249
17
        is_struct_style: bool,
250
17
        handler_name: &syn::Ident,
251
17
        enum_parameters: &[syn::Ident],
252
17
        handle_parameters: &[syn::Ident],
253
17
        context: &Context<'_>,
254
17
    ) -> TokenStream {
255
17
        let variant_name = &self.ident;
256
17
        let enum_name = context.enum_name;
257
17
        let await_suffix = context.await_suffix;
258
17
        if self.subaction {
259
1
            quote_spanned! {
260
1
                variant_name.span() => #enum_name::#variant_name(arg0) => {
261
1
                    self.handle_subaction(permissions, #(#enum_parameters),*)#await_suffix
262
1
                },
263
1
            }
264
16
        } else if is_struct_style {
265
5
            quote_spanned! {
266
5
                variant_name.span() => #enum_name::#variant_name{#(#enum_parameters),*} => {
267
5
                    <Self as #handler_name>::handle(#(#handle_parameters),*)#await_suffix
268
5
                },
269
5
            }
270
11
        } else if self.fields.is_empty() {
271
6
            quote_spanned! {
272
6
                variant_name.span() => #enum_name::#variant_name => {
273
6
                    <Self as #handler_name>::handle(#(#handle_parameters),*)#await_suffix
274
6
                }
275
6
            }
276
        } else {
277
5
            quote_spanned! {
278
5
                variant_name.span() => #enum_name::#variant_name(#(#enum_parameters),*) => {
279
5
                    <Self as #handler_name>::handle(#(#handle_parameters),*)#await_suffix
280
5
                },
281
5
            }
282
        }
283
17
    }
284
}
285

            
286
22
#[derive(Debug, FromField)]
287
struct Field {
288
    ident: Option<syn::Ident>,
289
    ty: syn::Type,
290
}
291

            
292
impl ToTokens for Actionable {
293
4
    fn to_tokens(&self, tokens: &mut TokenStream) {
294
4
        let enum_name = &self.ident;
295
4
        let enum_data = self
296
4
            .data
297
4
            .as_ref()
298
4
            .take_enum()
299
4
            .expect("Expected enum in data");
300

            
301
4
        let pub_tokens = match self.vis {
302
2
            syn::Visibility::Public(_) => quote! { pub },
303
            syn::Visibility::Crate(_) => quote! { pub(crate) },
304
2
            _ => TokenStream::default(),
305
        };
306

            
307
4
        let args = self.actionable.clone().unwrap_or_default();
308
4
        let actionable = actionable(args.actionable, enum_name.span());
309
4

            
310
4
        let mut handlers = Vec::new();
311
4
        let mut handler_names = Vec::new();
312
4
        let mut match_cases = Vec::new();
313
4

            
314
4
        let generated_dispatcher_name =
315
4
            syn::Ident::new(&format!("{}Dispatcher", enum_name), enum_name.span());
316
4

            
317
4
        let mut subaction = false;
318

            
319
4
        let (async_keyword, await_suffix, async_trait_attribute) = if args.asynchronous {
320
3
            (
321
3
                quote!(async),
322
3
                quote!(.await),
323
3
                quote!(#[#actionable::async_trait]),
324
3
            )
325
        } else {
326
1
            (quote!(), quote!(), quote!())
327
        };
328

            
329
4
        let context = Context {
330
4
            enum_name,
331
4
            generated_dispatcher_name: &generated_dispatcher_name,
332
4
            pub_tokens: &pub_tokens,
333
4
            actionable: &actionable,
334
4
            async_keyword: &async_keyword,
335
4
            await_suffix: &await_suffix,
336
4
            async_trait_attribute: &async_trait_attribute,
337
4
        };
338

            
339
21
        for variant in enum_data {
340
17
            let result = variant.generate_code(&context);
341
17
            match result.handler {
342
                VariantHandler::Subaction => {
343
1
                    if subaction {
344
                        abort!(self.ident, "only one subaction is allowed")
345
1
                    }
346
1
                    subaction = true;
347
                }
348
16
                VariantHandler::Handler { handler, name } => {
349
16
                    handlers.push(handler);
350
16
                    handler_names.push(name);
351
16
                }
352
            }
353
17
            match_cases.push(result.match_case);
354
        }
355

            
356
4
        let (subaction_type, subaction_handler) = if subaction {
357
1
            (quote!(<Self::Subaction>), quote! {
358
1
                type Subaction: Send;
359
1
                #async_keyword fn handle_subaction(&self, permissions: &#actionable::Permissions, subaction: Self::Subaction) -> Result<Self::Output, Self::Error>;
360
1
            })
361
        } else {
362
3
            (TokenStream::default(), TokenStream::default())
363
        };
364

            
365
4
        tokens.extend(quote! {
366
4
            #async_trait_attribute
367
4
            #[doc(hidden)]
368
4
            #pub_tokens trait #generated_dispatcher_name: Send + Sync {
369
4
                type Output: Send + Sync;
370
4
                type Error: From<#actionable::PermissionDenied> + Send + Sync;
371
4

            
372
4
                #async_keyword fn dispatch_to_handlers(&self, permissions: &#actionable::Permissions, request: #enum_name#subaction_type) -> Result<Self::Output, Self::Error>
373
4
                where Self: #(#handler_names)+* {
374
4
                    match request {
375
4
                        #(#match_cases)*
376
4
                    }
377
4
                }
378
4

            
379
4
                #subaction_handler
380
4
            }
381
4

            
382
4
            #(#handlers)*
383
4
        });
384
4
    }
385
}
386

            
387
4
pub fn derive(input: &syn::DeriveInput) -> Result<TokenStream, darling::Error> {
388
4
    let mut actionable = Actionable::from_derive_input(input)?;
389

            
390
4
    if let Some(attr) = input
391
4
        .attrs
392
4
        .iter()
393
5
        .find(|attr| attr.path.segments.first().unwrap().ident == "actionable")
394
3
    {
395
3
        let args: ActionableArgs = syn::parse2(attr.tokens.clone())?;
396
3
        actionable.actionable = Some(args);
397
1
    }
398

            
399
4
    Ok(actionable.into_token_stream())
400
4
}