mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-02-22 20:52:27 +00:00
Merge branch 'improve-error-messages' into 'master'
Improve error messages See merge request msrd0/gotham-restful!8
This commit is contained in:
commit
f6f16949a1
5 changed files with 201 additions and 90 deletions
|
@ -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<TokenStream2, Error>
|
||||
{
|
||||
let krate = super::krate();
|
||||
let input = parse_macro_input!(tokens as ItemStruct);
|
||||
let input = parse_macro_input::parse::<ItemStruct>(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()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
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,
|
||||
Attribute,
|
||||
AttributeArgs,
|
||||
Error,
|
||||
FnArg,
|
||||
ItemFn,
|
||||
Lit,
|
||||
|
@ -167,10 +169,19 @@ impl MethodArgumentType
|
|||
struct MethodArgument
|
||||
{
|
||||
ident : Ident,
|
||||
ident_span : Span,
|
||||
ty : MethodArgumentType
|
||||
}
|
||||
|
||||
fn interpret_arg_ty(index : usize, attrs : &[Attribute], name : &str, ty : Type) -> 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<MethodArgumentType, Error>
|
||||
{
|
||||
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 +190,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<MethodArgument, Error>
|
||||
{
|
||||
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, ident_span: arg.pat.span(), ty })
|
||||
}
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
|
@ -248,19 +259,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<TokenStream2, Error>
|
||||
{
|
||||
let krate = super::krate();
|
||||
|
||||
// parse attributes
|
||||
let mut method_attrs = parse_macro_input!(attrs as AttributeArgs);
|
||||
let mut method_attrs = parse_macro_input::parse::<AttributeArgs>(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::<ItemFn>(item)?;
|
||||
let fun_ident = &fun.sig.ident;
|
||||
let fun_vis = &fun.vis;
|
||||
|
||||
|
@ -282,16 +294,43 @@ 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<MethodArgument> = 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<MethodArgument> = Vec::new();
|
||||
let mut errors : Vec<Error> = 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<TokenStream2> = 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<TokenStream2> = 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;)
|
||||
|
@ -355,7 +394,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 +431,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()
|
||||
}
|
||||
|
|
|
@ -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<String>
|
||||
}
|
||||
|
||||
fn to_string(lit : &Lit) -> String
|
||||
fn to_string(lit : &Lit) -> Result<String, Error>
|
||||
{
|
||||
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<bool, Error>
|
||||
{
|
||||
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<Attrs, syn::Error>
|
||||
fn parse_attributes(input : &[Attribute]) -> Result<Attrs, Error>
|
||||
{
|
||||
let mut parsed = Attrs::default();
|
||||
for attr in input
|
||||
|
@ -111,13 +115,13 @@ fn parse_attributes(input : &[Attribute]) -> Result<Attrs, syn::Error>
|
|||
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<Attrs, syn::Error>
|
|||
Ok(parsed)
|
||||
}
|
||||
|
||||
fn expand_variant(variant : &Variant) -> TokenStream2
|
||||
fn expand_variant(variant : &Variant) -> Result<TokenStream2, Error>
|
||||
{
|
||||
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<TokenStream2, Error>
|
||||
{
|
||||
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<TokenStream2> = input.variants.iter().map(expand_variant).collect();
|
||||
let mut variants : Vec<TokenStream2> = Vec::new();
|
||||
let mut errors : Vec<Error> = 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<TokenStream2, Error>
|
||||
{
|
||||
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<TokenStream2, Error>
|
||||
{
|
||||
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<TokenStream2> = match input.fields {
|
||||
Fields::Named(fields) => {
|
||||
fields.named.iter().map(|field| expand_field(field)).collect()
|
||||
Fields::Named(named_fields) => {
|
||||
let mut fields : Vec<TokenStream2> = Vec::new();
|
||||
let mut errors : Vec<Error> = 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
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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<TokenStream2, Error>
|
||||
{
|
||||
let krate = super::krate();
|
||||
let input = parse_macro_input!(tokens as ItemStruct);
|
||||
let input = parse_macro_input::parse::<ItemStruct>(tokens)?;
|
||||
let ident = input.ident;
|
||||
let generics = input.generics;
|
||||
|
||||
let types : Vec<Path> = input.attrs.into_iter().filter(|attr|
|
||||
let mut types : Vec<Path> = Vec::new();
|
||||
let mut errors : Vec<Error> = 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::<MimeList>(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()
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue