1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-02-23 21:12:28 +00:00
deprecated-gotham-restful/gotham_restful_derive/src/method.rs

300 lines
7.5 KiB
Rust
Raw Normal View History

2019-10-27 20:44:23 +00:00
use heck::SnakeCase;
use proc_macro::TokenStream;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::{
Attribute,
FnArg,
ItemFn,
PatType,
ReturnType,
2020-01-14 23:16:31 +01:00
Type,
parse_macro_input
};
2019-10-06 15:03:30 +02:00
use std::str::FromStr;
pub enum Method
{
ReadAll,
Read,
2019-10-13 17:43:42 +02:00
Search,
Create,
UpdateAll,
Update,
DeleteAll,
Delete
}
2019-10-06 15:03:30 +02:00
impl FromStr for Method
{
type Err = String;
fn from_str(str : &str) -> Result<Self, Self::Err>
{
match str {
"ReadAll" | "read_all" => Ok(Self::ReadAll),
"Read" | "read" => Ok(Self::Read),
2019-10-13 17:43:42 +02:00
"Search" | "search" => Ok(Self::Search),
2019-10-06 15:03:30 +02:00
"Create" | "create" => Ok(Self::Create),
"UpdateAll" | "update_all" => Ok(Self::UpdateAll),
"Update" | "update" => Ok(Self::Update),
"DeleteAll" | "delete_all" => Ok(Self::DeleteAll),
"Delete" | "delete" => Ok(Self::Delete),
_ => Err("unknown method".to_string())
}
}
}
impl Method
{
2019-10-06 15:03:30 +02:00
pub fn trait_ident(&self) -> Ident
{
use Method::*;
let name = match self {
ReadAll => "ReadAll",
Read => "Read",
2019-10-13 17:43:42 +02:00
Search => "Search",
Create => "Create",
UpdateAll => "UpdateAll",
Update => "Update",
DeleteAll => "DeleteAll",
Delete => "Delete"
};
format_ident!("Resource{}", name)
}
2019-10-06 15:03:30 +02:00
pub fn fn_ident(&self) -> Ident
{
use Method::*;
let name = match self {
ReadAll => "read_all",
Read => "read",
2019-10-13 17:43:42 +02:00
Search => "search",
Create => "create",
UpdateAll => "update_all",
Update => "update",
DeleteAll => "delete_all",
Delete => "delete"
};
format_ident!("{}", name)
}
2019-10-06 15:03:30 +02:00
2019-10-27 20:44:23 +00:00
pub fn setup_ident(&self, resource : String) -> Ident
2019-10-06 15:03:30 +02:00
{
2019-10-27 20:44:23 +00:00
format_ident!("{}_{}_setup_impl", resource.to_snake_case(), self.fn_ident())
2019-10-06 15:03:30 +02:00
}
}
enum MethodArgumentType
{
StateRef,
StateMutRef,
MethodArg(Type),
DatabaseConnection(Type),
AuthStatus(Type),
AuthStatusRef(Type)
}
impl MethodArgumentType
{
fn is_method_arg(&self) -> bool
{
match self {
Self::MethodArg(_) => true,
_ => false,
}
}
fn is_database_conn(&self) -> bool
{
match self {
Self::DatabaseConnection(_) => true,
_ => false
}
}
fn is_auth_status(&self) -> bool
{
match self {
Self::AuthStatus(_) | Self::AuthStatusRef(_) => true,
_ => false
}
}
fn quote_ty(&self) -> Option<TokenStream2>
{
match self {
Self::MethodArg(ty) => Some(quote!(#ty)),
Self::DatabaseConnection(ty) => Some(quote!(#ty)),
Self::AuthStatus(ty) => Some(quote!(#ty)),
Self::AuthStatusRef(ty) => Some(quote!(#ty)),
_ => None
}
}
}
struct MethodArgument
{
ident : Ident,
ty : MethodArgumentType
}
fn interpret_arg_ty(index : usize, attrs : &[Attribute], name : &str, ty : Type) -> MethodArgumentType
{
let attr = attrs.into_iter()
.filter(|arg| arg.path.segments.iter().filter(|path| &path.ident.to_string() == "rest_arg").nth(0).is_some())
.nth(0)
.map(|arg| arg.tokens.to_string());
if cfg!(feature = "auth") && (attr.as_deref() == Some("auth") || (attr.is_none() && name == "auth"))
{
return 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 {
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")
};
}
MethodArgumentType::MethodArg(ty)
}
fn interpret_arg(index : usize, arg : &PatType) -> MethodArgument
{
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());
MethodArgument { ident, ty }
}
pub fn expand_method(method : Method, attrs : TokenStream, item : TokenStream) -> TokenStream
{
2019-10-14 02:17:25 +02:00
let krate = super::krate();
2019-10-27 20:44:23 +00:00
let resource_ident = parse_macro_input!(attrs as Ident);
let fun = parse_macro_input!(item as ItemFn);
2019-10-27 20:44:23 +00:00
let fun_ident = &fun.sig.ident;
let fun_vis = &fun.vis;
2019-10-27 20:44:23 +00:00
let trait_ident = method.trait_ident();
let method_ident = method.fn_ident();
let setup_ident = method.setup_ident(resource_ident.to_string());
let (ret, is_no_content) = match &fun.sig.output {
2019-10-14 02:17:25 +02:00
ReturnType::Default => (quote!(#krate::NoContent), true),
ReturnType::Type(_, ty) => (quote!(#ty), false)
};
2020-01-14 23:16:31 +01:00
// some default idents we'll need
2020-01-14 23:16:31 +01:00
let state_ident = format_ident!("state");
let repo_ident = format_ident!("repo");
let conn_ident = format_ident!("conn");
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();
2020-01-14 23:16:31 +01:00
// extract the generic parameters to use
let mut generics : Vec<TokenStream2> = args.iter()
.filter(|arg| (*arg).ty.is_method_arg())
.map(|arg| arg.ty.quote_ty().unwrap())
.collect();
generics.push(quote!(#ret));
2020-01-14 23:16:31 +01:00
// extract the definition of our method
let mut args_def : Vec<TokenStream2> = 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();
args_def.insert(0, quote!(#state_ident : &mut #krate::export::State));
2020-01-14 23:16:31 +01:00
// extract the arguments to pass over to the supplied method
let args_pass : Vec<TokenStream2> = args.iter().map(|arg| match (&arg.ty, &arg.ident) {
(MethodArgumentType::StateRef, _) => quote!(#state_ident),
(MethodArgumentType::StateMutRef, _) => quote!(#state_ident),
(MethodArgumentType::MethodArg(_), ident) => quote!(#ident),
(MethodArgumentType::DatabaseConnection(_), _) => quote!(&#conn_ident),
(MethodArgumentType::AuthStatus(_), _) => quote!(#auth_ident),
(MethodArgumentType::AuthStatusRef(_), _) => quote!(&#auth_ident)
2020-01-14 23:16:31 +01:00
}).collect();
// prepare the method block
let mut block = quote!(#fun_ident(#(#args_pass),*));
if is_no_content
{
block = quote!(#block; Default::default())
}
if let Some(arg) = args.iter().filter(|arg| (*arg).ty.is_database_conn()).nth(0)
{
let conn_ty = arg.ty.quote_ty();
block = quote! {
let #repo_ident = <#krate::export::Repo<#conn_ty>>::borrow_from(&#state_ident).clone();
#repo_ident.run::<_, #ret, ()>(move |#conn_ident| {
Ok({#block})
}).wait().unwrap()
};
}
if let Some(arg) = args.iter().filter(|arg| (*arg).ty.is_auth_status()).nth(0)
{
let auth_ty = arg.ty.quote_ty();
block = quote! {
let #auth_ident : #auth_ty = <#auth_ty>::borrow_from(#state_ident).clone();
#block
};
}
// prepare the where clause
let mut where_clause = quote!(#resource_ident : #krate::Resource,);
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,);
}
// put everything together
let output = quote! {
2019-10-27 20:44:23 +00:00
#fun
impl #krate::#trait_ident<#(#generics),*> for #resource_ident
where #where_clause
{
2019-10-27 20:44:23 +00:00
fn #method_ident(#(#args_def),*) -> #ret
{
#[allow(unused_imports)]
use #krate::export::{Future, FromState};
2019-10-27 20:44:23 +00:00
#block
}
}
2019-10-06 15:03:30 +02:00
#[deny(dead_code)]
2019-10-27 20:44:23 +00:00
#fun_vis fn #setup_ident<D : #krate::DrawResourceRoutes>(route : &mut D)
2019-10-06 15:03:30 +02:00
{
2019-10-27 20:44:23 +00:00
route.#method_ident::<#resource_ident, #(#generics),*>();
2019-10-06 15:03:30 +02:00
}
};
output.into()
}