actix_grants_proc_macro/
expand.rs1use 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}