azure-functions-codegen 0.4.0

Azure Functions for Rust code generation support
Documentation
use crate::func::TRIGGERS;
use crate::func::{OutputBindings, CONTEXT_TYPE_NAME};
use crate::util::{last_segment_in_path, to_camel_case};
use quote::{quote, ToTokens};
use syn::{FnArg, Ident, ItemFn, Pat, Type, TypeReference};

const INVOKER_PREFIX: &str = "__invoke_";

pub struct Invoker<'a>(pub &'a ItemFn);

impl<'a> Invoker<'a> {
    pub fn name(&self) -> String {
        format!("{}{}", INVOKER_PREFIX, self.0.ident)
    }

    fn get_args(&self) -> (Vec<&'a Ident>, Vec<&'a Type>) {
        self.iter_args()
            .filter_map(|(name, arg_type)| {
                if Invoker::is_context_type(&arg_type.elem) {
                    return None;
                }

                Some((name, &*arg_type.elem))
            })
            .unzip()
    }

    fn get_trigger_arg(&self) -> Option<(&'a Ident, &'a Type)> {
        self.iter_args()
            .find(|(_, arg_type)| Invoker::is_trigger_type(&arg_type.elem))
            .map(|(name, arg_type)| (name, &*arg_type.elem))
    }

    fn get_args_for_call(&self) -> Vec<::proc_macro2::TokenStream> {
        self.iter_args()
            .map(|(name, arg_type)| {
                if Invoker::is_context_type(&arg_type.elem) {
                    return quote!(__ctx);
                }

                let name_str = name.to_string();
                match arg_type.mutability {
                    Some(_) => quote!(#name.as_mut().expect(concat!("parameter binding '", #name_str, "' was not provided"))),
                    None => quote!(#name.as_ref().expect(concat!("parameter binding '", #name_str, "' was not provided")))
                }
            })
            .collect()
    }

    fn iter_args(&self) -> impl Iterator<Item = (&'a Ident, &'a TypeReference)> {
        self.0.decl.inputs.iter().map(|x| match x {
            FnArg::Captured(arg) => (
                match &arg.pat {
                    Pat::Ident(name) => &name.ident,
                    _ => panic!("expected ident argument pattern"),
                },
                match &arg.ty {
                    Type::Reference(tr) => tr,
                    _ => panic!("expected a type reference"),
                },
            ),
            _ => panic!("expected captured arguments"),
        })
    }

    fn is_context_type(ty: &Type) -> bool {
        match ty {
            Type::Path(tp) => last_segment_in_path(&tp.path).ident == CONTEXT_TYPE_NAME,
            Type::Paren(tp) => Invoker::is_context_type(&tp.elem),
            _ => false,
        }
    }

    fn is_trigger_type(ty: &Type) -> bool {
        match ty {
            Type::Path(tp) => {
                TRIGGERS.contains_key(last_segment_in_path(&tp.path).ident.to_string().as_str())
            }
            Type::Paren(tp) => Invoker::is_trigger_type(&tp.elem),
            _ => false,
        }
    }
}

impl ToTokens for Invoker<'_> {
    fn to_tokens(&self, tokens: &mut ::proc_macro2::TokenStream) {
        let invoker = Ident::new(
            &format!("{}{}", INVOKER_PREFIX, self.0.ident.to_string()),
            self.0.ident.span(),
        );
        let target = &self.0.ident;

        let (args, arg_types) = self.get_args();
        let args_for_match = args.clone();
        let (trigger_arg, _) = self
            .get_trigger_arg()
            .expect("the function must have a trigger");
        let binding_names: Vec<_> = args.iter().map(|x| to_camel_case(&x.to_string())).collect();

        let args_for_call = self.get_args_for_call();

        let output_bindings = OutputBindings(self.0);

        quote!(#[allow(dead_code)]
        fn #invoker(
            __name: &str,
            __req: &mut ::azure_functions::rpc::protocol::InvocationRequest,
        ) -> ::azure_functions::rpc::protocol::InvocationResponse {

            #(let mut #args: Option<#arg_types> = None;)*

            for __param in __req.input_data.iter_mut() {
                match __param.name.as_str() {
                   #(#binding_names => #args_for_match = Some(__param.take_data().into()),)*
                    _ => panic!(format!("unexpected parameter binding '{}'", __param.name)),
                };
            }

            use ::azure_functions::bindings::Trigger;
            match #trigger_arg.as_mut() {
                Some(t) => t.read_metadata(&mut __req.trigger_metadata),
                None => {}
            };

            let __ctx = &::azure_functions::Context::new(&__req.invocation_id, &__req.function_id, __name);
            let __ret = #target(#(#args_for_call,)*);

            let mut __res = ::azure_functions::rpc::protocol::InvocationResponse::new();
            __res.set_invocation_id(__req.invocation_id.clone());
            #output_bindings
            __res.mut_result().status =
                ::azure_functions::rpc::protocol::StatusResult_Status::Success;

            __res
        }).to_tokens(tokens);
    }
}