From 5954be324a5c8aefc4d77e367b17b22071018c46 Mon Sep 17 00:00:00 2001 From: Dominic Date: Tue, 7 Apr 2020 22:54:23 +0200 Subject: [PATCH 1/6] remove panics from derive(OpenapiType) --- gotham_restful_derive/src/openapi_type.rs | 105 ++++++++++++++-------- 1 file changed, 69 insertions(+), 36 deletions(-) diff --git a/gotham_restful_derive/src/openapi_type.rs b/gotham_restful_derive/src/openapi_type.rs index 90fcf2c..4fe1549 100644 --- a/gotham_restful_derive/src/openapi_type.rs +++ b/gotham_restful_derive/src/openapi_type.rs @@ -7,8 +7,10 @@ use proc_macro2::{ use quote::quote; use std::{iter, iter::FromIterator}; use syn::{ + spanned::Spanned, Attribute, AttributeArgs, + Error, Field, Fields, Generics, @@ -30,9 +32,11 @@ pub fn expand(tokens : TokenStream) -> TokenStream let output = match input { Item::Enum(item) => expand_enum(item), Item::Struct(item) => expand_struct(item), - _ => panic!("derive(OpenapiType) not supported for this context") + _ => Err(Error::new(input.span(), "derive(OpenapiType) not supported for this context")) }; - output.into() + output + .unwrap_or_else(|err| err.to_compile_error()) + .into() } fn expand_where(generics : &Generics) -> TokenStream2 @@ -65,19 +69,19 @@ struct Attrs rename : Option } -fn to_string(lit : &Lit) -> String +fn to_string(lit : &Lit) -> Result { match lit { - Lit::Str(str) => str.value(), - _ => panic!("Expected str, found {}", quote!(#lit)) + Lit::Str(str) => Ok(str.value()), + _ => Err(Error::new(lit.span(), "Expected string literal")) } } -fn to_bool(lit : &Lit) -> bool +fn to_bool(lit : &Lit) -> Result { match lit { - Lit::Bool(bool) => bool.value, - _ => panic!("Expected bool, found {}", quote!(#lit)) + Lit::Bool(bool) => Ok(bool.value), + _ => Err(Error::new(lit.span(), "Expected bool")) } } @@ -97,7 +101,7 @@ fn remove_parens(input : TokenStream2) -> TokenStream2 output } -fn parse_attributes(input : &[Attribute]) -> Result +fn parse_attributes(input : &[Attribute]) -> Result { let mut parsed = Attrs::default(); for attr in input @@ -111,13 +115,13 @@ fn parse_attributes(input : &[Attribute]) -> Result match &meta { NestedMeta::Meta(Meta::NameValue(kv)) => match kv.path.segments.last().map(|s| s.ident.to_string()) { Some(key) => match key.as_ref() { - "nullable" => parsed.nullable = to_bool(&kv.lit), - "rename" => parsed.rename = Some(to_string(&kv.lit)), - _ => panic!("Unexpected key: {}", key), + "nullable" => parsed.nullable = to_bool(&kv.lit)?, + "rename" => parsed.rename = Some(to_string(&kv.lit)?), + _ => return Err(Error::new(kv.path.span(), "Unknown key")), }, - _ => panic!("Unexpected token: {}", quote!(#meta)) + _ => return Err(Error::new(meta.span(), "Unexpected token")) }, - _ => panic!("Unexpected token: {}", quote!(#meta)) + _ => return Err(Error::new(meta.span(), "Unexpected token")) } } } @@ -125,43 +129,57 @@ fn parse_attributes(input : &[Attribute]) -> Result Ok(parsed) } -fn expand_variant(variant : &Variant) -> TokenStream2 +fn expand_variant(variant : &Variant) -> Result { if variant.fields != Fields::Unit { - panic!("Enum Variants with Fields not supported"); + return Err(Error::new(variant.span(), "Enum Variants with Fields not supported")); } let ident = &variant.ident; - let attrs = parse_attributes(&variant.attrs).expect("Unable to parse attributes"); + let attrs = parse_attributes(&variant.attrs)?; let name = match attrs.rename { Some(rename) => rename, None => ident.to_string() }; - quote! { + Ok(quote! { enumeration.push(#name.to_string()); - } + }) } -fn expand_enum(input : ItemEnum) -> TokenStream2 +fn expand_enum(input : ItemEnum) -> Result { let krate = super::krate(); let ident = input.ident; let generics = input.generics; let where_clause = expand_where(&generics); - let attrs = parse_attributes(&input.attrs).expect("Unable to parse attributes"); + let attrs = parse_attributes(&input.attrs)?; let nullable = attrs.nullable; let name = match attrs.rename { Some(rename) => rename, None => ident.to_string() }; - let variants : Vec = input.variants.iter().map(expand_variant).collect(); + let mut variants : Vec = Vec::new(); + let mut errors : Vec = Vec::new(); + for variant in input.variants.iter().map(expand_variant) + { + match variant { + Ok(variant) => variants.push(variant), + Err(e) => errors.push(e) + } + } + if !errors.is_empty() + { + let mut iter = errors.into_iter(); + let first = iter.nth(0).unwrap(); + return Err(iter.fold(first, |mut e0, e1| { e0.combine(e1); e0 })); + } - quote! { + Ok(quote! { impl #generics #krate::OpenapiType for #ident #generics #where_clause { @@ -187,25 +205,25 @@ fn expand_enum(input : ItemEnum) -> TokenStream2 } } } - } + }) } -fn expand_field(field : &Field) -> TokenStream2 +fn expand_field(field : &Field) -> Result { let ident = match &field.ident { Some(ident) => ident, - None => panic!("Fields without ident are not supported") + None => return Err(Error::new(field.span(), "Fields without ident are not supported")) }; let ty = &field.ty; - let attrs = parse_attributes(&field.attrs).expect("Unable to parse attributes"); + let attrs = parse_attributes(&field.attrs)?; let nullable = attrs.nullable; let name = match attrs.rename { Some(rename) => rename, None => ident.to_string() }; - quote! {{ + Ok(quote! {{ let mut schema = <#ty>::schema(); if schema.nullable @@ -242,17 +260,17 @@ fn expand_field(field : &Field) -> TokenStream2 ); } } - }} + }}) } -pub fn expand_struct(input : ItemStruct) -> TokenStream2 +pub fn expand_struct(input : ItemStruct) -> Result { let krate = super::krate(); let ident = input.ident; let generics = input.generics; let where_clause = expand_where(&generics); - let attrs = parse_attributes(&input.attrs).expect("Unable to parse attributes"); + let attrs = parse_attributes(&input.attrs)?; let nullable = attrs.nullable; let name = match attrs.rename { Some(rename) => rename, @@ -260,14 +278,29 @@ pub fn expand_struct(input : ItemStruct) -> TokenStream2 }; let fields : Vec = match input.fields { - Fields::Named(fields) => { - fields.named.iter().map(|field| expand_field(field)).collect() + Fields::Named(named_fields) => { + let mut fields : Vec = Vec::new(); + let mut errors : Vec = Vec::new(); + for field in named_fields.named.iter().map(expand_field) + { + match field { + Ok(field) => fields.push(field), + Err(e) => errors.push(e) + } + } + if !errors.is_empty() + { + let mut iter = errors.into_iter(); + let first = iter.nth(0).unwrap(); + return Err(iter.fold(first, |mut e0, e1| { e0.combine(e1); e0 })); + } + fields }, - Fields::Unnamed(_) => panic!("Unnamed fields are not supported"), + Fields::Unnamed(fields) => return Err(Error::new(fields.span(), "Unnamed fields are not supported")), Fields::Unit => Vec::new() }; - quote!{ + Ok(quote!{ impl #generics #krate::OpenapiType for #ident #generics #where_clause { @@ -297,5 +330,5 @@ pub fn expand_struct(input : ItemStruct) -> TokenStream2 } } } - } + }) } From 2b8ad485049e08fb6652f1fe6f5330d9be80c1c7 Mon Sep 17 00:00:00 2001 From: Dominic Date: Tue, 7 Apr 2020 23:01:26 +0200 Subject: [PATCH 2/6] remove panics from RequestBody --- gotham_restful_derive/src/request_body.rs | 37 +++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/gotham_restful_derive/src/request_body.rs b/gotham_restful_derive/src/request_body.rs index 74d1a72..930efa5 100644 --- a/gotham_restful_derive/src/request_body.rs +++ b/gotham_restful_derive/src/request_body.rs @@ -5,6 +5,7 @@ use syn::{ parse::{Parse, ParseStream, Result as SynResult}, punctuated::Punctuated, token::Comma, + Error, Generics, Ident, ItemStruct, @@ -53,19 +54,29 @@ fn impl_openapi_type(ident : &Ident, generics : &Generics) -> TokenStream2 } } -pub fn expand_request_body(tokens : TokenStream) -> TokenStream +fn expand(tokens : TokenStream) -> Result { let krate = super::krate(); - let input = parse_macro_input!(tokens as ItemStruct); + let input = parse_macro_input::parse::(tokens)?; let ident = input.ident; let generics = input.generics; - let types : Vec = input.attrs.into_iter().filter(|attr| + let mut types : Vec = Vec::new(); + let mut errors : Vec = Vec::new(); + for attr in input.attrs.into_iter().filter(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string()) // TODO wtf - ).flat_map(|attr| { - let m : MimeList = syn::parse2(attr.tokens).expect("unable to parse attributes"); - m.0.into_iter() - }).collect(); + ) { + match syn::parse2::(attr.tokens) { + Ok(m) => types.extend(m.0), + Err(e) => errors.push(e) + } + } + if !errors.is_empty() + { + let mut iter = errors.into_iter(); + let first = iter.nth(0).unwrap(); + return Err(iter.fold(first, |mut e0, e1| { e0.combine(e1); e0 })); + } let types = match types { ref types if types.is_empty() => quote!(None), @@ -74,7 +85,7 @@ pub fn expand_request_body(tokens : TokenStream) -> TokenStream let impl_openapi_type = impl_openapi_type(&ident, &generics); - let output = quote! { + Ok(quote! { impl #generics #krate::RequestBody for #ident #generics where #ident #generics : #krate::FromBody { @@ -85,6 +96,12 @@ pub fn expand_request_body(tokens : TokenStream) -> TokenStream } #impl_openapi_type - }; - output.into() + }) +} + +pub fn expand_request_body(tokens : TokenStream) -> TokenStream +{ + expand(tokens) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } From f677789747fd42a9ecf232bab1b999d16fceead4 Mon Sep 17 00:00:00 2001 From: Dominic Date: Wed, 8 Apr 2020 21:53:57 +0200 Subject: [PATCH 3/6] remove panics from FromBody --- gotham_restful_derive/src/from_body.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/gotham_restful_derive/src/from_body.rs b/gotham_restful_derive/src/from_body.rs index 633998f..78ac7d0 100644 --- a/gotham_restful_derive/src/from_body.rs +++ b/gotham_restful_derive/src/from_body.rs @@ -1,15 +1,25 @@ use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{ + spanned::Spanned, + Error, Fields, ItemStruct, parse_macro_input }; pub fn expand_from_body(tokens : TokenStream) -> TokenStream +{ + expand(tokens) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + +fn expand(tokens : TokenStream) -> Result { let krate = super::krate(); - let input = parse_macro_input!(tokens as ItemStruct); + let input = parse_macro_input::parse::(tokens)?; let ident = input.ident; let generics = input.generics; @@ -24,7 +34,7 @@ pub fn expand_from_body(tokens : TokenStream) -> TokenStream let field_ty = &field.ty; (quote!(where #field_ty : for<'a> From<&'a [u8]>), quote!(Self { #field_ident: body.into() })) }, - _ => panic!("FromBody can only be derived for structs with at most one field") + _ => return Err(Error::new(fields.into_iter().nth(1).unwrap().span(), "FromBody can only be derived for structs with at most one field")) } }, Fields::Unnamed(unnamed) => { @@ -36,13 +46,13 @@ pub fn expand_from_body(tokens : TokenStream) -> TokenStream let field_ty = &field.ty; (quote!(where #field_ty : for<'a> From<&'a [u8]>), quote!(Self(body.into()))) }, - _ => panic!("FromBody can only be derived for structs with at most one field") + _ => return Err(Error::new(fields.into_iter().nth(1).unwrap().span(), "FromBody can only be derived for structs with at most one field")) } }, Fields::Unit => (quote!(), quote!(Self{})) }; - let output = quote! { + Ok(quote! { impl #generics #krate::FromBody for #ident #generics #were { @@ -54,6 +64,5 @@ pub fn expand_from_body(tokens : TokenStream) -> TokenStream Ok(#body) } } - }; - output.into() + }) } From 381a230b8115d9d81c988928fdff24bbfb1f2628 Mon Sep 17 00:00:00 2001 From: Dominic Date: Wed, 8 Apr 2020 22:07:33 +0200 Subject: [PATCH 4/6] remove panics from expand_method --- gotham_restful_derive/src/method.rs | 70 +++++++++++++++++++---------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/gotham_restful_derive/src/method.rs b/gotham_restful_derive/src/method.rs index 6727171..bc05340 100644 --- a/gotham_restful_derive/src/method.rs +++ b/gotham_restful_derive/src/method.rs @@ -3,8 +3,10 @@ use proc_macro::TokenStream; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::{format_ident, quote}; use syn::{ + spanned::Spanned, Attribute, AttributeArgs, + Error, FnArg, ItemFn, Lit, @@ -170,7 +172,7 @@ struct MethodArgument ty : MethodArgumentType } -fn interpret_arg_ty(index : usize, attrs : &[Attribute], name : &str, ty : Type) -> MethodArgumentType +fn interpret_arg_ty(index : usize, attrs : &[Attribute], name : &str, ty : Type) -> Result { let attr = attrs.into_iter() .filter(|arg| arg.path.segments.iter().filter(|path| &path.ident.to_string() == "rest_arg").nth(0).is_some()) @@ -179,39 +181,39 @@ fn interpret_arg_ty(index : usize, attrs : &[Attribute], name : &str, ty : Type) if cfg!(feature = "auth") && (attr.as_deref() == Some("auth") || (attr.is_none() && name == "auth")) { - return match ty { + return Ok(match ty { Type::Reference(ty) => MethodArgumentType::AuthStatusRef(*ty.elem), ty => MethodArgumentType::AuthStatus(ty) - }; + }); } if cfg!(feature = "database") && (attr.as_deref() == Some("connection") || attr.as_deref() == Some("conn") || (attr.is_none() && name == "conn")) { - return MethodArgumentType::DatabaseConnection(match ty { + return Ok(MethodArgumentType::DatabaseConnection(match ty { Type::Reference(ty) => *ty.elem, ty => ty - }); + })); } if index == 0 { return match ty { - Type::Reference(ty) => if ty.mutability.is_none() { MethodArgumentType::StateRef } else { MethodArgumentType::StateMutRef }, - _ => panic!("The first argument, unless some feature is used, has to be a (mutable) reference to gotham::state::State") + Type::Reference(ty) => Ok(if ty.mutability.is_none() { MethodArgumentType::StateRef } else { MethodArgumentType::StateMutRef }), + _ => Err(Error::new(ty.span(), "The first argument, unless some feature is used, has to be a (mutable) reference to gotham::state::State")) }; } - MethodArgumentType::MethodArg(ty) + Ok(MethodArgumentType::MethodArg(ty)) } -fn interpret_arg(index : usize, arg : &PatType) -> MethodArgument +fn interpret_arg(index : usize, arg : &PatType) -> Result { let pat = &arg.pat; let ident = format_ident!("arg{}", index); let orig_name = quote!(#pat); - let ty = interpret_arg_ty(index, &arg.attrs, &orig_name.to_string(), *arg.ty.clone()); + let ty = interpret_arg_ty(index, &arg.attrs, &orig_name.to_string(), *arg.ty.clone())?; - MethodArgument { ident, ty } + Ok(MethodArgument { ident, ty }) } #[cfg(feature = "openapi")] @@ -248,19 +250,20 @@ fn expand_operation_id(_ : &AttributeArgs) -> TokenStream2 quote!() } -pub fn expand_method(method : Method, attrs : TokenStream, item : TokenStream) -> TokenStream +fn expand(method : Method, attrs : TokenStream, item : TokenStream) -> Result { let krate = super::krate(); // parse attributes - let mut method_attrs = parse_macro_input!(attrs as AttributeArgs); + let mut method_attrs = parse_macro_input::parse::(attrs)?; let resource_path = match method_attrs.remove(0) { NestedMeta::Meta(Meta::Path(path)) => path, - _ => panic!("Expected resource name for rest macro") + p => return Err(Error::new(p.span(), "Expected name of the Resource struct this method belongs to")) }; - let resource_name = resource_path.segments.last().expect("Resource name must not be empty").ident.to_string(); + let resource_name = resource_path.segments.last().map(|s| s.ident.to_string()) + .ok_or_else(|| Error::new(resource_path.span(), "Resource name must not be empty"))?; - let fun = parse_macro_input!(item as ItemFn); + let fun = parse_macro_input::parse::(item)?; let fun_ident = &fun.sig.ident; let fun_vis = &fun.vis; @@ -282,10 +285,25 @@ pub fn expand_method(method : Method, attrs : TokenStream, item : TokenStream) - let auth_ident = format_ident!("auth"); // extract arguments into pattern, ident and type - let args : Vec = fun.sig.inputs.iter().enumerate().map(|(i, arg)| match arg { - FnArg::Typed(arg) => interpret_arg(i, arg), - FnArg::Receiver(_) => panic!("didn't expect self parameter") - }).collect(); + let mut args : Vec = Vec::new(); + let mut errors : Vec = Vec::new(); + for (i, arg) in fun.sig.inputs.iter().enumerate() + { + let a = match arg { + FnArg::Typed(arg) => interpret_arg(i, arg), + FnArg::Receiver(_) => Err(Error::new(arg.span(), "Didn't expect self parameter")) + }; + match a { + Ok(a) => args.push(a), + Err(e) => errors.push(e) + } + } + if !errors.is_empty() + { + let mut iter = errors.into_iter(); + let first = iter.nth(0).unwrap(); + return Err(iter.fold(first, |mut e0, e1| { e0.combine(e1); e0 })); + } // extract the generic parameters to use let generics : Vec = args.iter() @@ -355,7 +373,7 @@ pub fn expand_method(method : Method, attrs : TokenStream, item : TokenStream) - let operation_id = expand_operation_id(&method_attrs); // put everything together - let output = quote! { + Ok(quote! { #fun #fun_vis mod #mod_ident @@ -392,6 +410,12 @@ pub fn expand_method(method : Method, attrs : TokenStream, item : TokenStream) - } } - }; - output.into() + }) +} + +pub fn expand_method(method : Method, attrs : TokenStream, item : TokenStream) -> TokenStream +{ + expand(method, attrs, item) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } From 6748130ff5dd0d27d998bacbdf881d71c2be7039 Mon Sep 17 00:00:00 2001 From: Dominic Date: Wed, 8 Apr 2020 22:18:06 +0200 Subject: [PATCH 5/6] easier debugging --- gotham_restful_derive/src/lib.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/gotham_restful_derive/src/lib.rs b/gotham_restful_derive/src/lib.rs index fbe5544..8657978 100644 --- a/gotham_restful_derive/src/lib.rs +++ b/gotham_restful_derive/src/lib.rs @@ -15,6 +15,13 @@ use resource::expand_resource; #[cfg(feature = "openapi")] mod openapi_type; +#[inline] +fn print_tokens(tokens : TokenStream) -> TokenStream +{ + //eprintln!("{}", tokens); + tokens +} + fn krate() -> TokenStream2 { quote!(::gotham_restful) @@ -23,72 +30,72 @@ fn krate() -> TokenStream2 #[proc_macro_derive(FromBody)] pub fn derive_from_body(tokens : TokenStream) -> TokenStream { - expand_from_body(tokens) + print_tokens(expand_from_body(tokens)) } #[cfg(feature = "openapi")] #[proc_macro_derive(OpenapiType, attributes(openapi))] pub fn derive_openapi_type(tokens : TokenStream) -> TokenStream { - openapi_type::expand(tokens) + print_tokens(openapi_type::expand(tokens)) } #[proc_macro_derive(RequestBody, attributes(supported_types))] pub fn derive_request_body(tokens : TokenStream) -> TokenStream { - expand_request_body(tokens) + print_tokens(expand_request_body(tokens)) } #[proc_macro_derive(Resource, attributes(rest_resource))] pub fn derive_resource(tokens : TokenStream) -> TokenStream { - expand_resource(tokens) + print_tokens(expand_resource(tokens)) } #[proc_macro_attribute] pub fn rest_read_all(attr : TokenStream, item : TokenStream) -> TokenStream { - expand_method(Method::ReadAll, attr, item) + print_tokens(expand_method(Method::ReadAll, attr, item)) } #[proc_macro_attribute] pub fn rest_read(attr : TokenStream, item : TokenStream) -> TokenStream { - expand_method(Method::Read, attr, item) + print_tokens(expand_method(Method::Read, attr, item)) } #[proc_macro_attribute] pub fn rest_search(attr : TokenStream, item : TokenStream) -> TokenStream { - expand_method(Method::Search, attr, item) + print_tokens(expand_method(Method::Search, attr, item)) } #[proc_macro_attribute] pub fn rest_create(attr : TokenStream, item : TokenStream) -> TokenStream { - expand_method(Method::Create, attr, item) + print_tokens(expand_method(Method::Create, attr, item)) } #[proc_macro_attribute] pub fn rest_update_all(attr : TokenStream, item : TokenStream) -> TokenStream { - expand_method(Method::UpdateAll, attr, item) + print_tokens(expand_method(Method::UpdateAll, attr, item)) } #[proc_macro_attribute] pub fn rest_update(attr : TokenStream, item : TokenStream) -> TokenStream { - expand_method(Method::Update, attr, item) + print_tokens(expand_method(Method::Update, attr, item)) } #[proc_macro_attribute] pub fn rest_delete_all(attr : TokenStream, item : TokenStream) -> TokenStream { - expand_method(Method::DeleteAll, attr, item) + print_tokens(expand_method(Method::DeleteAll, attr, item)) } #[proc_macro_attribute] pub fn rest_delete(attr : TokenStream, item : TokenStream) -> TokenStream { - expand_method(Method::Delete, attr, item) + print_tokens(expand_method(Method::Delete, attr, item)) } From 1d4d75c84a0c5e365454a096f03930885482013e Mon Sep 17 00:00:00 2001 From: Dominic Date: Sat, 11 Apr 2020 19:20:30 +0200 Subject: [PATCH 6/6] proper error message for too many / too few parameters --- gotham_restful_derive/src/method.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/gotham_restful_derive/src/method.rs b/gotham_restful_derive/src/method.rs index bc05340..6cfb185 100644 --- a/gotham_restful_derive/src/method.rs +++ b/gotham_restful_derive/src/method.rs @@ -1,6 +1,6 @@ use heck::{CamelCase, SnakeCase}; use proc_macro::TokenStream; -use proc_macro2::{Ident, TokenStream as TokenStream2}; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; use quote::{format_ident, quote}; use syn::{ spanned::Spanned, @@ -169,9 +169,18 @@ impl MethodArgumentType struct MethodArgument { ident : Ident, + ident_span : Span, ty : MethodArgumentType } +impl Spanned for MethodArgument +{ + fn span(&self) -> Span + { + self.ident_span + } +} + fn interpret_arg_ty(index : usize, attrs : &[Attribute], name : &str, ty : Type) -> Result { let attr = attrs.into_iter() @@ -213,7 +222,7 @@ fn interpret_arg(index : usize, arg : &PatType) -> Result let orig_name = quote!(#pat); let ty = interpret_arg_ty(index, &arg.attrs, &orig_name.to_string(), *arg.ty.clone())?; - Ok(MethodArgument { ident, ty }) + Ok(MethodArgument { ident, ident_span: arg.pat.span(), ty }) } #[cfg(feature = "openapi")] @@ -306,10 +315,22 @@ fn expand(method : Method, attrs : TokenStream, item : TokenStream) -> Result = args.iter() + let ty_names = method.type_names(); + let ty_len = ty_names.len(); + let generics_args : Vec<&MethodArgument> = args.iter() .filter(|arg| (*arg).ty.is_method_arg()) + .collect(); + if generics_args.len() > ty_len + { + return Err(Error::new(generics_args[ty_len].span(), "Too many arguments")); + } + else if generics_args.len() < ty_len + { + return Err(Error::new(fun_ident.span(), "Too few arguments")); + } + let generics : Vec = generics_args.iter() .map(|arg| arg.ty.quote_ty().unwrap()) - .zip(method.type_names()) + .zip(ty_names) .map(|(arg, name)| { let ident = format_ident!("{}", name); quote!(type #ident = #arg;)