1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-04-20 23:07:01 +00:00

update to gotham 0.5 and start using rustfmt

This commit is contained in:
Dominic 2020-09-15 15:10:41 +02:00
parent 5317e50961
commit d55b0897e9
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
39 changed files with 1798 additions and 2095 deletions

View file

@ -1,73 +1,65 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::cmp::min;
use syn::{
spanned::Spanned,
Data,
DeriveInput,
Error,
Field,
Fields,
Ident,
Result,
Type
};
use syn::{spanned::Spanned, Data, DeriveInput, Error, Field, Fields, Ident, Result, Type};
struct ParsedFields
{
fields : Vec<(Ident, Type)>,
named : bool
struct ParsedFields {
fields: Vec<(Ident, Type)>,
named: bool
}
impl ParsedFields
{
fn from_named<I>(fields : I) -> Self
impl ParsedFields {
fn from_named<I>(fields: I) -> Self
where
I : Iterator<Item = Field>
I: Iterator<Item = Field>
{
let fields = fields.map(|field| (field.ident.unwrap(), field.ty)).collect();
Self { fields, named: true }
}
fn from_unnamed<I>(fields : I) -> Self
fn from_unnamed<I>(fields: I) -> Self
where
I : Iterator<Item = Field>
I: Iterator<Item = Field>
{
let fields = fields.enumerate().map(|(i, field)| (format_ident!("arg{}", i), field.ty)).collect();
let fields = fields
.enumerate()
.map(|(i, field)| (format_ident!("arg{}", i), field.ty))
.collect();
Self { fields, named: false }
}
fn from_unit() -> Self
{
Self { fields: Vec::new(), named: false }
fn from_unit() -> Self {
Self {
fields: Vec::new(),
named: false
}
}
}
pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
{
pub fn expand_from_body(input: DeriveInput) -> Result<TokenStream> {
let krate = super::krate();
let ident = input.ident;
let generics = input.generics;
let strukt = match input.data {
Data::Enum(inum) => Err(inum.enum_token.span()),
Data::Struct(strukt) => Ok(strukt),
Data::Union(uni) => Err(uni.union_token.span())
}.map_err(|span| Error::new(span, "#[derive(FromBody)] only works for structs"))?;
}
.map_err(|span| Error::new(span, "#[derive(FromBody)] only works for structs"))?;
let fields = match strukt.fields {
Fields::Named(named) => ParsedFields::from_named(named.named.into_iter()),
Fields::Unnamed(unnamed) => ParsedFields::from_unnamed(unnamed.unnamed.into_iter()),
Fields::Unit => ParsedFields::from_unit()
};
let mut where_clause = quote!();
let mut block = quote!();
let mut body_ident = format_ident!("_body");
let mut type_ident = format_ident!("_type");
if let Some(body_field) = fields.fields.get(0)
{
if let Some(body_field) = fields.fields.get(0) {
body_ident = body_field.0.clone();
let body_ty = &body_field.1;
where_clause = quote! {
@ -80,9 +72,8 @@ pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
let #body_ident : #body_ty = #body_ident.into();
};
}
if let Some(type_field) = fields.fields.get(1)
{
if let Some(type_field) = fields.fields.get(1) {
type_ident = type_field.0.clone();
let type_ty = &type_field.1;
where_clause = quote! {
@ -94,9 +85,8 @@ pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
let #type_ident : #type_ty = #type_ident.into();
};
}
for field in &fields.fields[min(2, fields.fields.len())..]
{
for field in &fields.fields[min(2, fields.fields.len())..] {
let field_ident = &field.0;
let field_ty = &field.1;
where_clause = quote! {
@ -108,20 +98,20 @@ pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
let #field_ident : #field_ty = Default::default();
};
}
let field_names : Vec<&Ident> = fields.fields.iter().map(|field| &field.0).collect();
let field_names: Vec<&Ident> = fields.fields.iter().map(|field| &field.0).collect();
let ctor = if fields.named {
quote!(Self { #(#field_names),* })
} else {
quote!(Self ( #(#field_names),* ))
};
Ok(quote! {
impl #generics #krate::FromBody for #ident #generics
where #where_clause
{
type Err = ::std::convert::Infallible;
fn from_body(#body_ident : #krate::gotham::hyper::body::Bytes, #type_ident : #krate::Mime) -> Result<Self, ::std::convert::Infallible>
{
#block

View file

@ -21,114 +21,96 @@ mod openapi_type;
use openapi_type::expand_openapi_type;
#[inline]
fn print_tokens(tokens : TokenStream2) -> TokenStream
{
fn print_tokens(tokens: TokenStream2) -> TokenStream {
//eprintln!("{}", tokens);
tokens.into()
}
#[inline]
fn expand_derive<F>(input : TokenStream, expand : F) -> TokenStream
fn expand_derive<F>(input: TokenStream, expand: F) -> TokenStream
where
F : FnOnce(DeriveInput) -> Result<TokenStream2>
F: FnOnce(DeriveInput) -> Result<TokenStream2>
{
print_tokens(expand(parse_macro_input!(input))
.unwrap_or_else(|err| err.to_compile_error()))
print_tokens(expand(parse_macro_input!(input)).unwrap_or_else(|err| err.to_compile_error()))
}
#[inline]
fn expand_macro<F, A, I>(attrs : TokenStream, item : TokenStream, expand : F) -> TokenStream
fn expand_macro<F, A, I>(attrs: TokenStream, item: TokenStream, expand: F) -> TokenStream
where
F : FnOnce(A, I) -> Result<TokenStream2>,
A : ParseMacroInput,
I : ParseMacroInput
F: FnOnce(A, I) -> Result<TokenStream2>,
A: ParseMacroInput,
I: ParseMacroInput
{
print_tokens(expand(parse_macro_input!(attrs), parse_macro_input!(item))
.unwrap_or_else(|err| err.to_compile_error()))
print_tokens(expand(parse_macro_input!(attrs), parse_macro_input!(item)).unwrap_or_else(|err| err.to_compile_error()))
}
#[inline]
fn krate() -> TokenStream2
{
fn krate() -> TokenStream2 {
quote!(::gotham_restful)
}
#[proc_macro_derive(FromBody)]
pub fn derive_from_body(input : TokenStream) -> TokenStream
{
pub fn derive_from_body(input: TokenStream) -> TokenStream {
expand_derive(input, expand_from_body)
}
#[cfg(feature = "openapi")]
#[proc_macro_derive(OpenapiType, attributes(openapi))]
pub fn derive_openapi_type(input : TokenStream) -> TokenStream
{
pub fn derive_openapi_type(input: TokenStream) -> TokenStream {
expand_derive(input, expand_openapi_type)
}
#[proc_macro_derive(RequestBody, attributes(supported_types))]
pub fn derive_request_body(input : TokenStream) -> TokenStream
{
pub fn derive_request_body(input: TokenStream) -> TokenStream {
expand_derive(input, expand_request_body)
}
#[proc_macro_derive(Resource, attributes(resource))]
pub fn derive_resource(input : TokenStream) -> TokenStream
{
pub fn derive_resource(input: TokenStream) -> TokenStream {
expand_derive(input, expand_resource)
}
#[proc_macro_derive(ResourceError, attributes(display, from, status))]
pub fn derive_resource_error(input : TokenStream) -> TokenStream
{
pub fn derive_resource_error(input: TokenStream) -> TokenStream {
expand_derive(input, expand_resource_error)
}
#[proc_macro_attribute]
pub fn read_all(attr : TokenStream, item : TokenStream) -> TokenStream
{
pub fn read_all(attr: TokenStream, item: TokenStream) -> TokenStream {
expand_macro(attr, item, |attr, item| expand_method(Method::ReadAll, attr, item))
}
#[proc_macro_attribute]
pub fn read(attr : TokenStream, item : TokenStream) -> TokenStream
{
pub fn read(attr: TokenStream, item: TokenStream) -> TokenStream {
expand_macro(attr, item, |attr, item| expand_method(Method::Read, attr, item))
}
#[proc_macro_attribute]
pub fn search(attr : TokenStream, item : TokenStream) -> TokenStream
{
pub fn search(attr: TokenStream, item: TokenStream) -> TokenStream {
expand_macro(attr, item, |attr, item| expand_method(Method::Search, attr, item))
}
#[proc_macro_attribute]
pub fn create(attr : TokenStream, item : TokenStream) -> TokenStream
{
pub fn create(attr: TokenStream, item: TokenStream) -> TokenStream {
expand_macro(attr, item, |attr, item| expand_method(Method::Create, attr, item))
}
#[proc_macro_attribute]
pub fn change_all(attr : TokenStream, item : TokenStream) -> TokenStream
{
pub fn change_all(attr: TokenStream, item: TokenStream) -> TokenStream {
expand_macro(attr, item, |attr, item| expand_method(Method::ChangeAll, attr, item))
}
#[proc_macro_attribute]
pub fn change(attr : TokenStream, item : TokenStream) -> TokenStream
{
pub fn change(attr: TokenStream, item: TokenStream) -> TokenStream {
expand_macro(attr, item, |attr, item| expand_method(Method::Change, attr, item))
}
#[proc_macro_attribute]
pub fn remove_all(attr : TokenStream, item : TokenStream) -> TokenStream
{
pub fn remove_all(attr: TokenStream, item: TokenStream) -> TokenStream {
expand_macro(attr, item, |attr, item| expand_method(Method::RemoveAll, attr, item))
}
#[proc_macro_attribute]
pub fn remove(attr : TokenStream, item : TokenStream) -> TokenStream
{
pub fn remove(attr: TokenStream, item: TokenStream) -> TokenStream {
expand_macro(attr, item, |attr, item| expand_method(Method::Remove, attr, item))
}

View file

@ -2,26 +2,13 @@ use crate::util::CollectToResult;
use heck::{CamelCase, SnakeCase};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use syn::{
spanned::Spanned,
Attribute,
AttributeArgs,
Error,
FnArg,
ItemFn,
Lit,
LitBool,
Meta,
NestedMeta,
PatType,
Result,
ReturnType,
Type
};
use std::str::FromStr;
use syn::{
spanned::Spanned, Attribute, AttributeArgs, Error, FnArg, ItemFn, Lit, LitBool, Meta, NestedMeta, PatType, Result,
ReturnType, Type
};
pub enum Method
{
pub enum Method {
ReadAll,
Read,
Search,
@ -32,12 +19,10 @@ pub enum Method
Remove
}
impl FromStr for Method
{
impl FromStr for Method {
type Err = Error;
fn from_str(str : &str) -> Result<Self>
{
fn from_str(str: &str) -> Result<Self> {
match str {
"ReadAll" | "read_all" => Ok(Self::ReadAll),
"Read" | "read" => Ok(Self::Read),
@ -52,12 +37,10 @@ impl FromStr for Method
}
}
impl Method
{
pub fn type_names(&self) -> Vec<&'static str>
{
impl Method {
pub fn type_names(&self) -> Vec<&'static str> {
use Method::*;
match self {
ReadAll | RemoveAll => vec![],
Read | Remove => vec!["ID"],
@ -66,11 +49,10 @@ impl Method
Change => vec!["ID", "Body"]
}
}
pub fn trait_ident(&self) -> Ident
{
pub fn trait_ident(&self) -> Ident {
use Method::*;
let name = match self {
ReadAll => "ReadAll",
Read => "Read",
@ -83,11 +65,10 @@ impl Method
};
format_ident!("Resource{}", name)
}
pub fn fn_ident(&self) -> Ident
{
pub fn fn_ident(&self) -> Ident {
use Method::*;
let name = match self {
ReadAll => "read_all",
Read => "read",
@ -100,26 +81,26 @@ impl Method
};
format_ident!("{}", name)
}
pub fn mod_ident(&self, resource : &str) -> Ident
{
format_ident!("_gotham_restful_resource_{}_method_{}", resource.to_snake_case(), self.fn_ident())
pub fn mod_ident(&self, resource: &str) -> Ident {
format_ident!(
"_gotham_restful_resource_{}_method_{}",
resource.to_snake_case(),
self.fn_ident()
)
}
pub fn handler_struct_ident(&self, resource : &str) -> Ident
{
pub fn handler_struct_ident(&self, resource: &str) -> Ident {
format_ident!("{}{}Handler", resource.to_camel_case(), self.trait_ident())
}
pub fn setup_ident(&self, resource : &str) -> Ident
{
pub fn setup_ident(&self, resource: &str) -> Ident {
format_ident!("{}_{}_setup_impl", resource.to_snake_case(), self.fn_ident())
}
}
#[allow(clippy::large_enum_variant)]
enum MethodArgumentType
{
enum MethodArgumentType {
StateRef,
StateMutRef,
MethodArg(Type),
@ -128,116 +109,111 @@ enum MethodArgumentType
AuthStatusRef(Type)
}
impl MethodArgumentType
{
fn is_state_ref(&self) -> bool
{
impl MethodArgumentType {
fn is_state_ref(&self) -> bool {
matches!(self, Self::StateRef | Self::StateMutRef)
}
fn is_method_arg(&self) -> bool
{
fn is_method_arg(&self) -> bool {
matches!(self, Self::MethodArg(_))
}
fn is_database_conn(&self) -> bool
{
fn is_database_conn(&self) -> bool {
matches!(self, Self::DatabaseConnection(_))
}
fn is_auth_status(&self) -> bool
{
fn is_auth_status(&self) -> bool {
matches!(self, Self::AuthStatus(_) | Self::AuthStatusRef(_))
}
fn ty(&self) -> Option<&Type>
{
fn ty(&self) -> Option<&Type> {
match self {
Self::MethodArg(ty) | Self::DatabaseConnection(ty) | Self::AuthStatus(ty) | Self::AuthStatusRef(ty) => Some(ty),
_ => None
}
}
fn quote_ty(&self) -> Option<TokenStream>
{
fn quote_ty(&self) -> Option<TokenStream> {
self.ty().map(|ty| quote!(#ty))
}
}
struct MethodArgument
{
ident : Ident,
ident_span : Span,
ty : MethodArgumentType
struct MethodArgument {
ident: Ident,
ident_span: Span,
ty: MethodArgumentType
}
impl Spanned for MethodArgument
{
fn span(&self) -> Span
{
impl Spanned for MethodArgument {
fn span(&self) -> Span {
self.ident_span
}
}
fn interpret_arg_ty(attrs : &[Attribute], name : &str, ty : Type) -> Result<MethodArgumentType>
{
let attr = attrs.iter()
fn interpret_arg_ty(attrs: &[Attribute], name: &str, ty: Type) -> Result<MethodArgumentType> {
let attr = attrs
.iter()
.find(|arg| arg.path.segments.iter().any(|path| &path.ident.to_string() == "rest_arg"))
.map(|arg| arg.tokens.to_string());
// TODO issue a warning for _state usage once diagnostics become stable
if attr.as_deref() == Some("state") || (attr.is_none() && (name == "state" || name == "_state"))
{
if attr.as_deref() == Some("state") || (attr.is_none() && (name == "state" || name == "_state")) {
return match ty {
Type::Reference(ty) => Ok(if ty.mutability.is_none() { MethodArgumentType::StateRef } else { MethodArgumentType::StateMutRef }),
_ => Err(Error::new(ty.span(), "The state parameter has to be a (mutable) reference to gotham_restful::State"))
Type::Reference(ty) => Ok(if ty.mutability.is_none() {
MethodArgumentType::StateRef
} else {
MethodArgumentType::StateMutRef
}),
_ => Err(Error::new(
ty.span(),
"The state parameter has to be a (mutable) reference to gotham_restful::State"
))
};
}
if cfg!(feature = "auth") && (attr.as_deref() == Some("auth") || (attr.is_none() && name == "auth"))
{
if cfg!(feature = "auth") && (attr.as_deref() == Some("auth") || (attr.is_none() && name == "auth")) {
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"))
if cfg!(feature = "database")
&& (attr.as_deref() == Some("connection") || attr.as_deref() == Some("conn") || (attr.is_none() && name == "conn"))
{
return Ok(MethodArgumentType::DatabaseConnection(match ty {
Type::Reference(ty) => *ty.elem,
ty => ty
}));
}
Ok(MethodArgumentType::MethodArg(ty))
}
fn interpret_arg(index : usize, arg : &PatType) -> Result<MethodArgument>
{
fn interpret_arg(index: usize, arg: &PatType) -> Result<MethodArgument> {
let pat = &arg.pat;
let ident = format_ident!("arg{}", index);
let orig_name = quote!(#pat);
let ty = interpret_arg_ty(&arg.attrs, &orig_name.to_string(), *arg.ty.clone())?;
Ok(MethodArgument { ident, ident_span: arg.pat.span(), ty })
Ok(MethodArgument {
ident,
ident_span: arg.pat.span(),
ty
})
}
#[cfg(feature = "openapi")]
fn expand_operation_id(attrs : &[NestedMeta]) -> TokenStream
{
let mut operation_id : Option<&Lit> = None;
for meta in attrs
{
if let NestedMeta::Meta(Meta::NameValue(kv)) = meta
{
if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("operation_id".to_owned())
{
fn expand_operation_id(attrs: &[NestedMeta]) -> TokenStream {
let mut operation_id: Option<&Lit> = None;
for meta in attrs {
if let NestedMeta::Meta(Meta::NameValue(kv)) = meta {
if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("operation_id".to_owned()) {
operation_id = Some(&kv.lit)
}
}
}
match operation_id {
Some(operation_id) => quote! {
fn operation_id() -> Option<String>
@ -250,26 +226,24 @@ fn expand_operation_id(attrs : &[NestedMeta]) -> TokenStream
}
#[cfg(not(feature = "openapi"))]
fn expand_operation_id(_ : &[NestedMeta]) -> TokenStream
{
fn expand_operation_id(_: &[NestedMeta]) -> TokenStream {
quote!()
}
fn expand_wants_auth(attrs : &[NestedMeta], default : bool) -> TokenStream
{
let default_lit = Lit::Bool(LitBool { value: default, span: Span::call_site() });
fn expand_wants_auth(attrs: &[NestedMeta], default: bool) -> TokenStream {
let default_lit = Lit::Bool(LitBool {
value: default,
span: Span::call_site()
});
let mut wants_auth = &default_lit;
for meta in attrs
{
if let NestedMeta::Meta(Meta::NameValue(kv)) = meta
{
if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("wants_auth".to_owned())
{
for meta in attrs {
if let NestedMeta::Meta(Meta::NameValue(kv)) = meta {
if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("wants_auth".to_owned()) {
wants_auth = &kv.lit
}
}
}
quote! {
fn wants_auth() -> bool
{
@ -279,73 +253,80 @@ fn expand_wants_auth(attrs : &[NestedMeta], default : bool) -> TokenStream
}
#[allow(clippy::comparison_chain)]
pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -> Result<TokenStream>
{
pub fn expand_method(method: Method, mut attrs: AttributeArgs, fun: ItemFn) -> Result<TokenStream> {
let krate = super::krate();
// parse attributes
if attrs.len() < 1
{
return Err(Error::new(Span::call_site(), "Missing Resource struct. Example: #[read_all(MyResource)]"));
if attrs.len() < 1 {
return Err(Error::new(
Span::call_site(),
"Missing Resource struct. Example: #[read_all(MyResource)]"
));
}
let resource_path = match attrs.remove(0) {
NestedMeta::Meta(Meta::Path(path)) => path,
p => return Err(Error::new(p.span(), "Expected name of the Resource struct this method belongs to"))
p => {
return Err(Error::new(
p.span(),
"Expected name of the Resource struct this method belongs to"
))
},
};
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 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_ident = &fun.sig.ident;
let fun_vis = &fun.vis;
let fun_is_async = fun.sig.asyncness.is_some();
if let Some(unsafety) = fun.sig.unsafety
{
if let Some(unsafety) = fun.sig.unsafety {
return Err(Error::new(unsafety.span(), "Resource methods must not be unsafe"));
}
let trait_ident = method.trait_ident();
let method_ident = method.fn_ident();
let mod_ident = method.mod_ident(&resource_name);
let handler_ident = method.handler_struct_ident(&resource_name);
let setup_ident = method.setup_ident(&resource_name);
let (ret, is_no_content) = match &fun.sig.output {
ReturnType::Default => (quote!(#krate::NoContent), true),
ReturnType::Type(_, ty) => (quote!(#ty), false)
};
// some default idents we'll need
let state_ident = format_ident!("state");
let repo_ident = format_ident!("repo");
let conn_ident = format_ident!("conn");
let auth_ident = format_ident!("auth");
let res_ident = format_ident!("res");
// extract arguments into pattern, ident and type
let args = fun.sig.inputs.iter()
let args = fun
.sig
.inputs
.iter()
.enumerate()
.map(|(i, arg)| match arg {
FnArg::Typed(arg) => interpret_arg(i, arg),
FnArg::Receiver(_) => Err(Error::new(arg.span(), "Didn't expect self parameter"))
})
.collect_to_result()?;
// extract the generic parameters to use
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
{
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
{
} else if generics_args.len() < ty_len {
return Err(Error::new(fun_ident.span(), "Too few arguments"));
}
let generics : Vec<TokenStream> = generics_args.iter()
let generics: Vec<TokenStream> = generics_args
.iter()
.map(|arg| arg.ty.quote_ty().unwrap())
.zip(ty_names)
.map(|(arg, name)| {
@ -353,47 +334,53 @@ pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -
quote!(type #ident = #arg;)
})
.collect();
// extract the definition of our method
let mut args_def : Vec<TokenStream> = args.iter()
let mut args_def: Vec<TokenStream> = args
.iter()
.filter(|arg| (*arg).ty.is_method_arg())
.map(|arg| {
let ident = &arg.ident;
let ty = arg.ty.quote_ty();
quote!(#ident : #ty)
}).collect();
})
.collect();
args_def.insert(0, quote!(mut #state_ident : #krate::State));
// extract the arguments to pass over to the supplied method
let args_pass : Vec<TokenStream> = args.iter().map(|arg| match (&arg.ty, &arg.ident) {
(MethodArgumentType::StateRef, _) => quote!(&#state_ident),
(MethodArgumentType::StateMutRef, _) => quote!(&mut #state_ident),
(MethodArgumentType::MethodArg(_), ident) => quote!(#ident),
(MethodArgumentType::DatabaseConnection(_), _) => quote!(&#conn_ident),
(MethodArgumentType::AuthStatus(_), _) => quote!(#auth_ident),
(MethodArgumentType::AuthStatusRef(_), _) => quote!(&#auth_ident)
}).collect();
let args_pass: Vec<TokenStream> = args
.iter()
.map(|arg| match (&arg.ty, &arg.ident) {
(MethodArgumentType::StateRef, _) => quote!(&#state_ident),
(MethodArgumentType::StateMutRef, _) => quote!(&mut #state_ident),
(MethodArgumentType::MethodArg(_), ident) => quote!(#ident),
(MethodArgumentType::DatabaseConnection(_), _) => quote!(&#conn_ident),
(MethodArgumentType::AuthStatus(_), _) => quote!(#auth_ident),
(MethodArgumentType::AuthStatusRef(_), _) => quote!(&#auth_ident)
})
.collect();
// prepare the method block
let mut block = quote!(#fun_ident(#(#args_pass),*));
let mut state_block = quote!();
if fun_is_async
{
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_state_ref())
{
return Err(Error::new(arg.span(), "async fn must not take &State as an argument as State is not Sync, consider boxing"));
if fun_is_async {
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_state_ref()) {
return Err(Error::new(
arg.span(),
"async fn must not take &State as an argument as State is not Sync, consider boxing"
));
}
block = quote!(#block.await);
}
if is_no_content
{
if is_no_content {
block = quote!(#block; Default::default())
}
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_database_conn())
{
if fun_is_async
{
return Err(Error::new(arg.span(), "async fn is not supported when database support is required, consider boxing"));
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_database_conn()) {
if fun_is_async {
return Err(Error::new(
arg.span(),
"async fn is not supported when database support is required, consider boxing"
));
}
let conn_ty = arg.ty.quote_ty();
state_block = quote! {
@ -411,70 +398,68 @@ pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -
}
};
}
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_auth_status())
{
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_auth_status()) {
let auth_ty = arg.ty.quote_ty();
state_block = quote! {
#state_block
let #auth_ident : #auth_ty = <#auth_ty>::borrow_from(&#state_ident).clone();
};
}
// prepare the where clause
let mut where_clause = quote!(#resource_path : #krate::Resource,);
for arg in args.iter().filter(|arg| (*arg).ty.is_auth_status())
{
for arg in args.iter().filter(|arg| (*arg).ty.is_auth_status()) {
let auth_ty = arg.ty.quote_ty();
where_clause = quote!(#where_clause #auth_ty : Clone,);
}
// attribute generated code
let operation_id = expand_operation_id(&attrs);
let wants_auth = expand_wants_auth(&attrs, args.iter().any(|arg| (*arg).ty.is_auth_status()));
// put everything together
Ok(quote! {
#fun
#fun_vis mod #mod_ident
{
use super::*;
struct #handler_ident;
impl #krate::ResourceMethod for #handler_ident
{
type Res = #ret;
#operation_id
#wants_auth
}
impl #krate::#trait_ident for #handler_ident
where #where_clause
{
#(#generics)*
fn #method_ident(#(#args_def),*) -> std::pin::Pin<Box<dyn std::future::Future<Output = (#krate::State, #ret)> + Send>>
{
#[allow(unused_imports)]
use #krate::{export::FutureExt, FromState};
#state_block
async move {
let #res_ident = { #block };
(#state_ident, #res_ident)
}.boxed()
}
}
#[deny(dead_code)]
pub fn #setup_ident<D : #krate::DrawResourceRoutes>(route : &mut D)
{
route.#method_ident::<#handler_ident>();
}
}
})
}

View file

@ -1,99 +1,77 @@
use crate::util::{CollectToResult, remove_parens};
use crate::util::{remove_parens, CollectToResult};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{
parse_macro_input,
spanned::Spanned,
Attribute,
AttributeArgs,
Data,
DataEnum,
DataStruct,
DeriveInput,
Error,
Field,
Fields,
Generics,
GenericParam,
Lit,
LitStr,
Meta,
NestedMeta,
Result,
Variant
parse_macro_input, spanned::Spanned, Attribute, AttributeArgs, Data, DataEnum, DataStruct, DeriveInput, Error, Field,
Fields, GenericParam, Generics, Lit, LitStr, Meta, NestedMeta, Result, Variant
};
pub fn expand_openapi_type(input : DeriveInput) -> Result<TokenStream>
{
pub fn expand_openapi_type(input: DeriveInput) -> Result<TokenStream> {
match (input.ident, input.generics, input.attrs, input.data) {
(ident, generics, attrs, Data::Enum(inum)) => expand_enum(ident, generics, attrs, inum),
(ident, generics, attrs, Data::Struct(strukt)) => expand_struct(ident, generics, attrs, strukt),
(_, _, _, Data::Union(uni)) => Err(Error::new(uni.union_token.span(), "#[derive(OpenapiType)] only works for structs and enums"))
(_, _, _, Data::Union(uni)) => Err(Error::new(
uni.union_token.span(),
"#[derive(OpenapiType)] only works for structs and enums"
))
}
}
fn expand_where(generics : &Generics) -> TokenStream
{
if generics.params.is_empty()
{
fn expand_where(generics: &Generics) -> TokenStream {
if generics.params.is_empty() {
return quote!();
}
let krate = super::krate();
let idents = generics.params.iter()
let idents = generics
.params
.iter()
.map(|param| match param {
GenericParam::Type(ty) => Some(ty.ident.clone()),
_ => None
})
.filter(|param| param.is_some())
.map(|param| param.unwrap());
quote! {
where #(#idents : #krate::OpenapiType),*
}
}
#[derive(Debug, Default)]
struct Attrs
{
nullable : bool,
rename : Option<String>
struct Attrs {
nullable: bool,
rename: Option<String>
}
fn to_string(lit : &Lit) -> Result<String>
{
fn to_string(lit: &Lit) -> Result<String> {
match lit {
Lit::Str(str) => Ok(str.value()),
_ => Err(Error::new(lit.span(), "Expected string literal"))
}
}
fn to_bool(lit : &Lit) -> Result<bool>
{
fn to_bool(lit: &Lit) -> Result<bool> {
match lit {
Lit::Bool(bool) => Ok(bool.value),
_ => Err(Error::new(lit.span(), "Expected bool"))
}
}
fn parse_attributes(input : &[Attribute]) -> Result<Attrs>
{
fn parse_attributes(input: &[Attribute]) -> Result<Attrs> {
let mut parsed = Attrs::default();
for attr in input
{
if attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("openapi".to_owned())
{
for attr in input {
if attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("openapi".to_owned()) {
let tokens = remove_parens(attr.tokens.clone());
// TODO this is not public api but syn currently doesn't offer another convenient way to parse AttributeArgs
let nested = parse_macro_input::parse::<AttributeArgs>(tokens.into())?;
for meta in nested
{
for meta in nested {
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)?),
_ => return Err(Error::new(kv.path.span(), "Unknown key")),
"rename" => parsed.rename = Some(to_string(&kv.lit)?),
_ => return Err(Error::new(kv.path.span(), "Unknown key"))
},
_ => return Err(Error::new(meta.span(), "Unexpected token"))
},
@ -105,42 +83,40 @@ fn parse_attributes(input : &[Attribute]) -> Result<Attrs>
Ok(parsed)
}
fn expand_variant(variant : &Variant) -> Result<TokenStream>
{
if variant.fields != Fields::Unit
{
return Err(Error::new(variant.span(), "#[derive(OpenapiType)] does not support enum variants with fields"));
fn expand_variant(variant: &Variant) -> Result<TokenStream> {
if variant.fields != Fields::Unit {
return Err(Error::new(
variant.span(),
"#[derive(OpenapiType)] does not support enum variants with fields"
));
}
let ident = &variant.ident;
let attrs = parse_attributes(&variant.attrs)?;
let name = match attrs.rename {
Some(rename) => rename,
None => ident.to_string()
};
Ok(quote! {
enumeration.push(#name.to_string());
})
}
fn expand_enum(ident : Ident, generics : Generics, attrs : Vec<Attribute>, input : DataEnum) -> Result<TokenStream>
{
fn expand_enum(ident: Ident, generics: Generics, attrs: Vec<Attribute>, input: DataEnum) -> Result<TokenStream> {
let krate = super::krate();
let where_clause = expand_where(&generics);
let attrs = parse_attributes(&attrs)?;
let nullable = attrs.nullable;
let name = match attrs.rename {
Some(rename) => rename,
None => ident.to_string()
};
let variants = input.variants.iter()
.map(expand_variant)
.collect_to_result()?;
let variants = input.variants.iter().map(expand_variant).collect_to_result()?;
Ok(quote! {
impl #generics #krate::OpenapiType for #ident #generics
#where_clause
@ -148,11 +124,11 @@ fn expand_enum(ident : Ident, generics : Generics, attrs : Vec<Attribute>, input
fn schema() -> #krate::OpenapiSchema
{
use #krate::{export::openapi::*, OpenapiSchema};
let mut enumeration : Vec<String> = Vec::new();
#(#variants)*
let schema = SchemaKind::Type(Type::String(StringType {
format: VariantOrUnknownOrEmpty::Empty,
enumeration,
@ -170,25 +146,29 @@ fn expand_enum(ident : Ident, generics : Generics, attrs : Vec<Attribute>, input
})
}
fn expand_field(field : &Field) -> Result<TokenStream>
{
fn expand_field(field: &Field) -> Result<TokenStream> {
let ident = match &field.ident {
Some(ident) => ident,
None => return Err(Error::new(field.span(), "#[derive(OpenapiType)] does not support fields without an ident"))
None => {
return Err(Error::new(
field.span(),
"#[derive(OpenapiType)] does not support fields without an ident"
))
},
};
let ident_str = LitStr::new(&ident.to_string(), ident.span());
let ty = &field.ty;
let attrs = parse_attributes(&field.attrs)?;
let nullable = attrs.nullable;
let name = match attrs.rename {
Some(rename) => rename,
None => ident.to_string()
};
Ok(quote! {{
let mut schema = <#ty>::schema();
if schema.nullable
{
schema.nullable = false;
@ -197,7 +177,7 @@ fn expand_field(field : &Field) -> Result<TokenStream>
{
required.push(#ident_str.to_string());
}
let keys : Vec<String> = schema.dependencies.keys().map(|k| k.to_string()).collect();
for dep in keys
{
@ -207,7 +187,7 @@ fn expand_field(field : &Field) -> Result<TokenStream>
dependencies.insert(dep, dep_schema);
}
}
match schema.name.clone() {
Some(schema_name) => {
properties.insert(
@ -226,42 +206,42 @@ fn expand_field(field : &Field) -> Result<TokenStream>
}})
}
fn expand_struct(ident : Ident, generics : Generics, attrs : Vec<Attribute>, input : DataStruct) -> Result<TokenStream>
{
fn expand_struct(ident: Ident, generics: Generics, attrs: Vec<Attribute>, input: DataStruct) -> Result<TokenStream> {
let krate = super::krate();
let where_clause = expand_where(&generics);
let attrs = parse_attributes(&attrs)?;
let nullable = attrs.nullable;
let name = match attrs.rename {
Some(rename) => rename,
None => ident.to_string()
};
let fields : Vec<TokenStream> = match input.fields {
Fields::Named(named_fields) => {
named_fields.named.iter()
.map(expand_field)
.collect_to_result()?
let fields: Vec<TokenStream> = match input.fields {
Fields::Named(named_fields) => named_fields.named.iter().map(expand_field).collect_to_result()?,
Fields::Unnamed(fields) => {
return Err(Error::new(
fields.span(),
"#[derive(OpenapiType)] does not support unnamed fields"
))
},
Fields::Unnamed(fields) => return Err(Error::new(fields.span(), "#[derive(OpenapiType)] does not support unnamed fields")),
Fields::Unit => Vec::new()
};
Ok(quote!{
Ok(quote! {
impl #generics #krate::OpenapiType for #ident #generics
#where_clause
{
fn schema() -> #krate::OpenapiSchema
{
use #krate::{export::{openapi::*, IndexMap}, OpenapiSchema};
let mut properties : IndexMap<String, ReferenceOr<Box<Schema>>> = IndexMap::new();
let mut required : Vec<String> = Vec::new();
let mut dependencies : IndexMap<String, OpenapiSchema> = IndexMap::new();
#(#fields)*
let schema = SchemaKind::Type(Type::Object(ObjectType {
properties,
required,

View file

@ -6,43 +6,34 @@ use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
DeriveInput,
Error,
Generics,
Path,
Result,
Token
DeriveInput, Error, Generics, Path, Result, Token
};
struct MimeList(Punctuated<Path, Token![,]>);
impl Parse for MimeList
{
fn parse(input: ParseStream) -> Result<Self>
{
impl Parse for MimeList {
fn parse(input: ParseStream) -> Result<Self> {
let list = Punctuated::parse_separated_nonempty(&input)?;
Ok(Self(list))
}
}
#[cfg(not(feature = "openapi"))]
fn impl_openapi_type(_ident : &Ident, _generics : &Generics) -> TokenStream
{
fn impl_openapi_type(_ident: &Ident, _generics: &Generics) -> TokenStream {
quote!()
}
#[cfg(feature = "openapi")]
fn impl_openapi_type(ident : &Ident, generics : &Generics) -> TokenStream
{
fn impl_openapi_type(ident: &Ident, generics: &Generics) -> TokenStream {
let krate = super::krate();
quote! {
impl #generics #krate::OpenapiType for #ident #generics
{
fn schema() -> #krate::OpenapiSchema
{
use #krate::{export::openapi::*, OpenapiSchema};
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary),
..Default::default()
@ -52,32 +43,38 @@ fn impl_openapi_type(ident : &Ident, generics : &Generics) -> TokenStream
}
}
pub fn expand_request_body(input : DeriveInput) -> Result<TokenStream>
{
pub fn expand_request_body(input: DeriveInput) -> Result<TokenStream> {
let krate = super::krate();
let ident = input.ident;
let generics = input.generics;
let types = input.attrs.into_iter()
.filter(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string()))
let types = input
.attrs
.into_iter()
.filter(|attr| {
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string())
})
.flat_map(|attr| {
let span = attr.span();
attr.parse_args::<MimeList>()
.map(|list| Box::new(list.0.into_iter().map(Ok)) as Box<dyn Iterator<Item = Result<Path>>>)
.unwrap_or_else(|mut err| {
err.combine(Error::new(span, "Hint: Types list should look like #[supported_types(TEXT_PLAIN, APPLICATION_JSON)]"));
err.combine(Error::new(
span,
"Hint: Types list should look like #[supported_types(TEXT_PLAIN, APPLICATION_JSON)]"
));
Box::new(iter::once(Err(err)))
})
})
.collect_to_result()?;
let types = match types {
ref types if types.is_empty() => quote!(None),
types => quote!(Some(vec![#(#types),*]))
};
let impl_openapi_type = impl_openapi_type(&ident, &generics);
Ok(quote! {
impl #generics #krate::RequestBody for #ident #generics
where #ident #generics : #krate::FromBody
@ -87,7 +84,7 @@ pub fn expand_request_body(input : DeriveInput) -> Result<TokenStream>
#types
}
}
#impl_openapi_type
})
}

View file

@ -1,23 +1,18 @@
use crate::{method::Method, util::CollectToResult};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use std::{iter, str::FromStr};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
DeriveInput,
Error,
Result,
Token
DeriveInput, Error, Result, Token
};
use std::{iter, str::FromStr};
struct MethodList(Punctuated<Ident, Token![,]>);
impl Parse for MethodList
{
fn parse(input: ParseStream) -> Result<Self>
{
impl Parse for MethodList {
fn parse(input: ParseStream) -> Result<Self> {
let content;
let _paren = parenthesized!(content in input);
let list = Punctuated::parse_separated_nonempty(&content)?;
@ -25,26 +20,32 @@ impl Parse for MethodList
}
}
pub fn expand_resource(input : DeriveInput) -> Result<TokenStream>
{
pub fn expand_resource(input: DeriveInput) -> Result<TokenStream> {
let krate = super::krate();
let ident = input.ident;
let name = ident.to_string();
let methods = input.attrs.into_iter().filter(|attr|
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("resource".to_string()) // TODO wtf
).map(|attr| {
syn::parse2(attr.tokens).map(|m : MethodList| m.0.into_iter())
}).flat_map(|list| match list {
Ok(iter) => Box::new(iter.map(|method| {
let method = Method::from_str(&method.to_string()).map_err(|err| Error::new(method.span(), err))?;
let mod_ident = method.mod_ident(&name);
let ident = method.setup_ident(&name);
Ok(quote!(#mod_ident::#ident(&mut route);))
})) as Box<dyn Iterator<Item = Result<TokenStream>>>,
Err(err) => Box::new(iter::once(Err(err)))
}).collect_to_result()?;
let methods =
input
.attrs
.into_iter()
.filter(
|attr| {
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("resource".to_string())
} // TODO wtf
)
.map(|attr| syn::parse2(attr.tokens).map(|m: MethodList| m.0.into_iter()))
.flat_map(|list| match list {
Ok(iter) => Box::new(iter.map(|method| {
let method = Method::from_str(&method.to_string()).map_err(|err| Error::new(method.span(), err))?;
let mod_ident = method.mod_ident(&name);
let ident = method.setup_ident(&name);
Ok(quote!(#mod_ident::#ident(&mut route);))
})) as Box<dyn Iterator<Item = Result<TokenStream>>>,
Err(err) => Box::new(iter::once(Err(err)))
})
.collect_to_result()?;
Ok(quote! {
impl #krate::Resource for #ident
{

View file

@ -1,68 +1,54 @@
use crate::util::{CollectToResult, remove_parens};
use crate::util::{remove_parens, CollectToResult};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use std::iter;
use syn::{
spanned::Spanned,
Attribute,
Data,
DeriveInput,
Error,
Fields,
GenericParam,
LitStr,
Path,
PathSegment,
Result,
Type,
spanned::Spanned, Attribute, Data, DeriveInput, Error, Fields, GenericParam, LitStr, Path, PathSegment, Result, Type,
Variant
};
struct ErrorVariantField
{
attrs : Vec<Attribute>,
ident : Ident,
ty : Type
struct ErrorVariantField {
attrs: Vec<Attribute>,
ident: Ident,
ty: Type
}
struct ErrorVariant
{
ident : Ident,
status : Option<Path>,
is_named : bool,
fields : Vec<ErrorVariantField>,
from_ty : Option<(usize, Type)>,
display : Option<LitStr>
struct ErrorVariant {
ident: Ident,
status: Option<Path>,
is_named: bool,
fields: Vec<ErrorVariantField>,
from_ty: Option<(usize, Type)>,
display: Option<LitStr>
}
fn process_variant(variant : Variant) -> Result<ErrorVariant>
{
let status = match variant.attrs.iter()
.find(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("status".to_string()))
{
Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?),
None => None
};
fn process_variant(variant: Variant) -> Result<ErrorVariant> {
let status =
match variant.attrs.iter().find(|attr| {
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("status".to_string())
}) {
Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?),
None => None
};
let mut is_named = false;
let mut fields = Vec::new();
match variant.fields {
Fields::Named(named) => {
is_named = true;
for field in named.named
{
for field in named.named {
let span = field.span();
fields.push(ErrorVariantField {
attrs: field.attrs,
ident: field.ident.ok_or_else(|| Error::new(span, "Missing ident for this enum variant field"))?,
ident: field
.ident
.ok_or_else(|| Error::new(span, "Missing ident for this enum variant field"))?,
ty: field.ty
});
}
},
Fields::Unnamed(unnamed) => {
for (i, field) in unnamed.unnamed.into_iter().enumerate()
{
for (i, field) in unnamed.unnamed.into_iter().enumerate() {
fields.push(ErrorVariantField {
attrs: field.attrs,
ident: format_ident!("arg{}", i),
@ -72,19 +58,25 @@ fn process_variant(variant : Variant) -> Result<ErrorVariant>
},
Fields::Unit => {}
}
let from_ty = fields.iter()
let from_ty = fields
.iter()
.enumerate()
.find(|(_, field)| field.attrs.iter().any(|attr| attr.path.segments.last().map(|segment| segment.ident.to_string()) == Some("from".to_string())))
.find(|(_, field)| {
field
.attrs
.iter()
.any(|attr| attr.path.segments.last().map(|segment| segment.ident.to_string()) == Some("from".to_string()))
})
.map(|(i, field)| (i, field.ty.clone()));
let display = match variant.attrs.iter()
.find(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("display".to_string()))
{
let display = match variant.attrs.iter().find(|attr| {
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("display".to_string())
}) {
Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?),
None => None
};
Ok(ErrorVariant {
ident: variant.ident,
status,
@ -95,18 +87,15 @@ fn process_variant(variant : Variant) -> Result<ErrorVariant>
})
}
fn path_segment(name : &str) -> PathSegment
{
fn path_segment(name: &str) -> PathSegment {
PathSegment {
ident: format_ident!("{}", name),
arguments: Default::default()
}
}
impl ErrorVariant
{
fn fields_pat(&self) -> TokenStream
{
impl ErrorVariant {
fn fields_pat(&self) -> TokenStream {
let mut fields = self.fields.iter().map(|field| &field.ident).peekable();
if fields.peek().is_none() {
quote!()
@ -116,74 +105,90 @@ impl ErrorVariant
quote!( ( #( #fields ),* ) )
}
}
fn to_display_match_arm(&self, formatter_ident : &Ident, enum_ident : &Ident) -> Result<TokenStream>
{
fn to_display_match_arm(&self, formatter_ident: &Ident, enum_ident: &Ident) -> Result<TokenStream> {
let ident = &self.ident;
let display = self.display.as_ref().ok_or_else(|| Error::new(self.ident.span(), "Missing display string for this variant"))?;
let display = self
.display
.as_ref()
.ok_or_else(|| Error::new(self.ident.span(), "Missing display string for this variant"))?;
// lets find all required format parameters
let display_str = display.value();
let mut params : Vec<&str> = Vec::new();
let mut params: Vec<&str> = Vec::new();
let len = display_str.len();
let mut start = len;
let mut iter = display_str.chars().enumerate().peekable();
while let Some((i, c)) = iter.next()
{
while let Some((i, c)) = iter.next() {
// we found a new opening brace
if start == len && c == '{'
{
if start == len && c == '{' {
start = i + 1;
}
// we found a duplicate opening brace
else if start == i && c == '{'
{
else if start == i && c == '{' {
start = len;
}
// we found a closing brace
else if start < i && c == '}'
{
else if start < i && c == '}' {
match iter.peek() {
Some((_, '}')) => return Err(Error::new(display.span(), "Error parsing format string: curly braces not allowed inside parameter name")),
Some((_, '}')) => {
return Err(Error::new(
display.span(),
"Error parsing format string: curly braces not allowed inside parameter name"
))
},
_ => params.push(&display_str[start..i])
};
start = len;
}
// we found a closing brace without content
else if start == i && c == '}'
{
return Err(Error::new(display.span(), "Error parsing format string: parameter name must not be empty"))
else if start == i && c == '}' {
return Err(Error::new(
display.span(),
"Error parsing format string: parameter name must not be empty"
));
}
}
if start != len
{
return Err(Error::new(display.span(), "Error parsing format string: Unmatched opening brace"));
if start != len {
return Err(Error::new(
display.span(),
"Error parsing format string: Unmatched opening brace"
));
}
let params = params.into_iter().map(|name| format_ident!("{}{}", if self.is_named { "" } else { "arg" }, name));
let params = params
.into_iter()
.map(|name| format_ident!("{}{}", if self.is_named { "" } else { "arg" }, name));
let fields_pat = self.fields_pat();
Ok(quote! {
#enum_ident::#ident #fields_pat => write!(#formatter_ident, #display #(, #params = #params)*)
})
}
fn into_match_arm(self, krate : &TokenStream, enum_ident : &Ident) -> Result<TokenStream>
{
fn into_match_arm(self, krate: &TokenStream, enum_ident: &Ident) -> Result<TokenStream> {
let ident = &self.ident;
let fields_pat = self.fields_pat();
let status = self.status.map(|status| {
// the status might be relative to StatusCode, so let's fix that
if status.leading_colon.is_none() && status.segments.len() < 2
{
if status.leading_colon.is_none() && status.segments.len() < 2 {
let status_ident = status.segments.first().cloned().unwrap_or_else(|| path_segment("OK"));
Path {
leading_colon: Some(Default::default()),
segments: vec![path_segment("gotham_restful"), path_segment("gotham"), path_segment("hyper"), path_segment("StatusCode"), status_ident].into_iter().collect()
segments: vec![
path_segment("gotham_restful"),
path_segment("gotham"),
path_segment("hyper"),
path_segment("StatusCode"),
status_ident,
]
.into_iter()
.collect()
}
} else {
status
}
else { status }
});
// the response will come directly from the from_ty if present
let res = match (self.from_ty, status) {
(Some((from_index, _)), None) => {
@ -198,14 +203,13 @@ impl ErrorVariant
})),
(None, None) => return Err(Error::new(ident.span(), "Missing #[status(code)] for this variant"))
};
Ok(quote! {
#enum_ident::#ident #fields_pat => #res
})
}
fn were(&self) -> Option<TokenStream>
{
fn were(&self) -> Option<TokenStream> {
match self.from_ty.as_ref() {
Some((_, ty)) => Some(quote!( #ty : ::std::error::Error )),
None => None
@ -213,22 +217,22 @@ impl ErrorVariant
}
}
pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
{
pub fn expand_resource_error(input: DeriveInput) -> Result<TokenStream> {
let krate = super::krate();
let ident = input.ident;
let generics = input.generics;
let inum = match input.data {
Data::Enum(inum) => Ok(inum),
Data::Struct(strukt) => Err(strukt.struct_token.span()),
Data::Union(uni) => Err(uni.union_token.span())
}.map_err(|span| Error::new(span, "#[derive(ResourceError)] only works for enums"))?;
let variants = inum.variants.into_iter()
.map(process_variant)
.collect_to_result()?;
let display_impl = if variants.iter().any(|v| v.display.is_none()) { None } else {
}
.map_err(|span| Error::new(span, "#[derive(ResourceError)] only works for enums"))?;
let variants = inum.variants.into_iter().map(process_variant).collect_to_result()?;
let display_impl = if variants.iter().any(|v| v.display.is_none()) {
None
} else {
let were = generics.params.iter().filter_map(|param| match param {
GenericParam::Type(ty) => {
let ident = &ty.ident;
@ -237,7 +241,8 @@ pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
_ => None
});
let formatter_ident = format_ident!("resource_error_display_formatter");
let match_arms = variants.iter()
let match_arms = variants
.iter()
.map(|v| v.to_display_match_arm(&formatter_ident, &ident))
.collect_to_result()?;
Some(quote! {
@ -253,34 +258,39 @@ pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
}
})
};
let mut from_impls : Vec<TokenStream> = Vec::new();
for var in &variants
{
let mut from_impls: Vec<TokenStream> = Vec::new();
for var in &variants {
let var_ident = &var.ident;
let (from_index, from_ty) = match var.from_ty.as_ref() {
Some(f) => f,
None => continue
};
let from_ident = &var.fields[*from_index].ident;
let fields_pat = var.fields_pat();
let fields_where = var.fields.iter().enumerate()
let fields_where = var
.fields
.iter()
.enumerate()
.filter(|(i, _)| i != from_index)
.map(|(_, field)| {
let ty = &field.ty;
quote!( #ty : Default )
})
.chain(iter::once(quote!( #from_ty : ::std::error::Error )));
let fields_let = var.fields.iter().enumerate()
let fields_let = var
.fields
.iter()
.enumerate()
.filter(|(i, _)| i != from_index)
.map(|(_, field)| {
let id = &field.ident;
let ty = &field.ty;
quote!( let #id : #ty = Default::default(); )
});
from_impls.push(quote! {
impl #generics ::std::convert::From<#from_ty> for #ident #generics
where #( #fields_where ),*
@ -293,20 +303,21 @@ pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
}
});
}
let were = variants.iter().filter_map(|variant| variant.were()).collect::<Vec<_>>();
let variants = variants.into_iter()
let variants = variants
.into_iter()
.map(|variant| variant.into_match_arm(&krate, &ident))
.collect_to_result()?;
.collect_to_result()?;
Ok(quote! {
#display_impl
impl #generics #krate::IntoResponseError for #ident #generics
where #( #were ),*
{
type Err = #krate::export::serde_json::Error;
fn into_response_error(self) -> Result<#krate::Response, Self::Err>
{
match self {
@ -314,7 +325,7 @@ pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
}
}
}
#( #from_impls )*
})
}

View file

@ -2,40 +2,38 @@ use proc_macro2::{Delimiter, TokenStream, TokenTree};
use std::iter;
use syn::Error;
pub trait CollectToResult
{
pub trait CollectToResult {
type Item;
fn collect_to_result(self) -> Result<Vec<Self::Item>, Error>;
}
impl<Item, I> CollectToResult for I
where
I : Iterator<Item = Result<Item, Error>>
I: Iterator<Item = Result<Item, Error>>
{
type Item = Item;
fn collect_to_result(self) -> Result<Vec<Item>, Error>
{
self.fold(<Result<Vec<Item>, Error>>::Ok(Vec::new()), |res, code| {
match (code, res) {
(Ok(code), Ok(mut codes)) => { codes.push(code); Ok(codes) },
(Ok(_), Err(errors)) => Err(errors),
(Err(err), Ok(_)) => Err(err),
(Err(err), Err(mut errors)) => { errors.combine(err); Err(errors) }
}
fn collect_to_result(self) -> Result<Vec<Item>, Error> {
self.fold(<Result<Vec<Item>, Error>>::Ok(Vec::new()), |res, code| match (code, res) {
(Ok(code), Ok(mut codes)) => {
codes.push(code);
Ok(codes)
},
(Ok(_), Err(errors)) => Err(errors),
(Err(err), Ok(_)) => Err(err),
(Err(err), Err(mut errors)) => {
errors.combine(err);
Err(errors)
}
})
}
}
pub fn remove_parens(input : TokenStream) -> TokenStream
{
pub fn remove_parens(input: TokenStream) -> TokenStream {
let iter = input.into_iter().flat_map(|tt| {
if let TokenTree::Group(group) = &tt
{
if group.delimiter() == Delimiter::Parenthesis
{
if let TokenTree::Group(group) = &tt {
if group.delimiter() == Delimiter::Parenthesis {
return Box::new(group.stream().into_iter()) as Box<dyn Iterator<Item = TokenTree>>;
}
}