protect-endpoints-proc-macro 0.3.2

A proc-macro way to protect your endpoints
Documentation
use crate::expand::ProtectEndpoint;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use syn::{parse_quote, PathArguments, ReturnType, Type};

impl ProtectEndpoint {
    pub(super) fn to_tokens_poem(&self, output: &mut TokenStream2) {
        let func_vis = &self.func.vis();
        let func_block = &self.func.block();
        let fn_sig = &self.func.sig();
        let fn_attrs = &self.func.attrs();

        let fn_name = &fn_sig.ident;
        let fn_generics = &fn_sig.generics;
        let fn_async = &fn_sig.asyncness;

        let ty = self
            .args
            .ty
            .as_ref()
            .map(ToTokens::to_token_stream)
            .unwrap_or(quote! {String});

        let mut fn_args = fn_sig.inputs.clone();
        let auth_details = format!("_auth_details_{}", fn_args.len());
        let auth_details: Ident = Ident::new(&auth_details, Span::call_site());

        fn_args.push(parse_quote!(#auth_details: poem_grants::authorities::AuthDetails<#ty>));

        let (original_out, fn_output) = match &fn_sig.output {
            ReturnType::Type(ref _arrow, ref ty) => {
                let fn_out = Some(ty.as_ref())
                    .and_then(|ty| {
                        if let Type::Path(ty_path) = ty {
                            ty_path.path.segments.last()
                        } else {
                            None
                        }
                    })
                    .filter(|last_seg| last_seg.ident == "Result")
                    .and_then(|last_seg| {
                        if let PathArguments::AngleBracketed(angle_args) =
                            last_seg.clone().arguments
                        {
                            if let Some(syn::GenericArgument::Type(res_out_ty)) =
                                angle_args.args.first()
                            {
                                Some(res_out_ty.to_token_stream())
                            } else {
                                None
                            }
                        } else {
                            None
                        }
                    })
                    .unwrap_or_else(|| ty.to_token_stream());

                (ty.to_token_stream(), fn_out)
            }
            ReturnType::Default => (quote! {()}, quote! {()}),
        };

        let condition = self
            .args
            .cond
            .to_tokens(&auth_details, self.args.ty.is_some());
        let condition = quote!(if #condition);

        let err_resp = if let Some(expr) = &self.args.error_fn {
            quote!(#expr())
        } else {
            quote!(poem::Error::from(
                poem_grants::error::AccessError::ForbiddenRequest
            ))
        };

        let body = if fn_async.is_some() {
            quote! {
                let f = || async move #func_block;
                let val: #original_out = f().await;
                val.into_result()
            }
        } else {
            quote! {
                let f = move || #func_block;
                let val: #original_out = f();
                val.into_result()
            }
        };

        let stream = quote! {
            #(#fn_attrs)*
            #func_vis #fn_async fn #fn_name #fn_generics(
                #fn_args
            ) -> poem::Result<#fn_output> {
                use poem::error::IntoResult;
                use poem_grants::authorities::AuthoritiesCheck;
                #condition {
                    #body
                } else {
                    Err(#err_resp)
                }
            }
        };

        output.extend(stream);
    }
}