1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
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); } }