actix_grants_proc_macro/
expand.rs

1use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
2use quote::{quote, ToTokens};
3use syn::{AttributeArgs, ItemFn, NestedMeta, ReturnType};
4
5pub(crate) struct HasPermissions {
6    check_fn: Ident,
7    func: ItemFn,
8    args: Args,
9}
10
11impl HasPermissions {
12    pub fn new(check_fn: &str, args: AttributeArgs, func: ItemFn) -> syn::Result<Self> {
13        let check_fn: Ident = syn::parse_str(check_fn)?;
14
15        let args = Args::new(args)?;
16        if args.permissions.is_empty() {
17            return Err(syn::Error::new(
18                Span::call_site(),
19                "The #[has_permissions(..)] macro requires at least one `permission` argument",
20            ));
21        }
22
23        Ok(Self {
24            check_fn,
25            func,
26            args,
27        })
28    }
29}
30
31impl ToTokens for HasPermissions {
32    fn to_tokens(&self, output: &mut TokenStream2) {
33        let func_vis = &self.func.vis;
34        let func_block = &self.func.block;
35
36        let fn_sig = &self.func.sig;
37        let fn_attrs = &self.func.attrs;
38        let fn_name = &fn_sig.ident;
39        let fn_generics = &fn_sig.generics;
40        let fn_args = &fn_sig.inputs;
41        let fn_async = &fn_sig.asyncness.unwrap();
42        let fn_output = match &fn_sig.output {
43            ReturnType::Type(ref _arrow, ref ty) => ty.to_token_stream(),
44            ReturnType::Default => {
45                quote! {()}
46            }
47        };
48
49        let check_fn = &self.check_fn;
50
51        let args = if self.args.type_.is_some() {
52            let permissions: Vec<syn::Expr> = self
53                .args
54                .permissions
55                .iter()
56                .map(|perm| perm.parse().unwrap())
57                .collect();
58
59            quote! {
60                #(&#permissions,)*
61            }
62        } else {
63            let permissions = &self.args.permissions;
64
65            quote! {
66                #(#permissions,)*
67            }
68        };
69
70        let type_ = self
71            .args
72            .type_
73            .as_ref()
74            .map(|t| t.to_token_stream())
75            .unwrap_or(quote! {String});
76
77        let arg_name = format!("_auth_details_{}", fn_args.len());
78        let arg_name = syn::Ident::new(&arg_name, Span::call_site());
79
80        let condition = if let Some(expr) = &self.args.secure {
81            quote!(if #arg_name.#check_fn(&[#args]) && #expr)
82        } else {
83            quote!(if #arg_name.#check_fn(&[#args]))
84        };
85
86        let resp = if let Some(expr) = &self.args.error_fn {
87            quote!(#expr())
88        } else {
89            quote!(actix_web::HttpResponse::Forbidden().finish())
90        };
91
92        let stream = quote! {
93            #(#fn_attrs)*
94            #func_vis #fn_async fn #fn_name #fn_generics(
95                #arg_name: actix_web_grants::permissions::AuthDetails<#type_>,
96                #fn_args
97            ) -> actix_web::Either<#fn_output, actix_web::HttpResponse> {
98                use actix_web_grants::permissions::{PermissionsCheck, RolesCheck};
99                #condition {
100                    let f = || async move #func_block;
101                    actix_web::Either::Left(f().await)
102                } else {
103                    actix_web::Either::Right(#resp)
104                }
105            }
106        };
107
108        output.extend(stream);
109    }
110}
111
112struct Args {
113    permissions: Vec<syn::LitStr>,
114    secure: Option<syn::Expr>,
115    type_: Option<syn::Expr>,
116    error_fn: Option<syn::Ident>,
117}
118
119impl Args {
120    fn new(args: AttributeArgs) -> syn::Result<Self> {
121        let mut permissions = Vec::with_capacity(args.len());
122        let mut secure = None;
123        let mut error_fn = None;
124        let mut type_ = None;
125        for arg in args {
126            match arg {
127                NestedMeta::Lit(syn::Lit::Str(lit)) => {
128                    permissions.push(lit);
129                }
130                NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
131                    path,
132                    lit: syn::Lit::Str(lit_str),
133                    ..
134                })) => {
135                    if path.is_ident("secure") {
136                        let expr = lit_str.parse().unwrap();
137                        secure = Some(expr);
138                    } else if path.is_ident("type") {
139                        let expr = lit_str.parse().unwrap();
140                        type_ = Some(expr);
141                    } else if path.is_ident("error") {
142                        let expr = lit_str.parse().unwrap();
143                        error_fn = Some(expr);
144                    } else {
145                        return Err(syn::Error::new_spanned(
146                            path,
147                            "Unknown identifier. Available: 'secure', 'type' and 'error'",
148                        ));
149                    }
150                }
151                _ => {
152                    return Err(syn::Error::new_spanned(arg, "Unknown attribute."));
153                }
154            }
155        }
156
157        Ok(Args {
158            permissions,
159            secure,
160            type_,
161            error_fn,
162        })
163    }
164}