#![allow(unused_imports)]
#![allow(unused_variables)]
#![allow(unused_macros)]
#![allow(dead_code)]
#[macro_use]
extern crate syn;
#[macro_use]
extern crate quote;
use proc_macro::TokenStream;
use syn::export::Span;
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{parse_macro_input, token, Expr, Ident, Token, Type, Visibility};
use syn::{FnArg, Receiver};
//enum authorize {}
//
//impl Middleware for authorize {
// type Output =
//}
//
#[derive(Debug)]
enum Param {
Filter(syn::TypePath),
Body(syn::TypePath),
Query(syn::TypePath),
}
#[derive(Default)]
struct Route {
query: Option<syn::TypePath>,
body: Option<syn::TypePath>,
// e.g. ("show" / u64)
path: Option<syn::export::TokenStream2>,
// e.g. warp::get()
verb: Option<syn::Expr>,
//param: Option<Param>,
filter: Option<syn::TypePath>,
}
// Given a route impl in #[routes], translate to a warp::Filter
fn create_route(struct_ty: &syn::Type, method: &syn::ImplItemMethod) -> syn::export::TokenStream2 {
// e.g. UserController::list
let static_method_call: syn::Expr = {
let method_name = method.sig.ident.to_string();
let call_path = format!("Self::{}", method_name);
syn::parse_str(call_path.as_str()).expect("Call path parsable")
};
let filter = {
if let Some(attr) = method.attrs.iter().find(|attr| {
attr.path
.segments
.last()
.expect("Should have last if in pos")
.ident
.to_string()
== "filter"
}) {
let ty = attr.parse_args::<syn::TypePath>().unwrap().clone();
Some(ty)
} else {
None
}
};
let body = {
if let Some(attr) = method.attrs.iter().find(|attr| {
attr.path
.segments
.last()
.expect("Should have last if in pos")
.ident
.to_string()
== "body"
}) {
let ty = attr.parse_args::<syn::TypePath>().unwrap().clone();
Some(ty)
} else {
None
}
};
let query = {
if let Some(attr) = method.attrs.iter().find(|attr| {
attr.path
.segments
.last()
.expect("Should have last here, too!")
.ident
.to_string()
== "query"
}) {
let ty = attr
.parse_args::<syn::TypePath>()
.expect("Should have typepath at this point")
.clone();
Some(ty)
} else {
None
}
};
// the route annotation
let verb_attr = method
.attrs
.clone()
.into_iter()
.find(|attr| {
match attr
.path
.segments
.first()
.expect("Verb attr expected first ")
.ident
.to_string()
.as_str()
{
"get" => true,
"post" => true,
"put" => true,
_ => false,
}
})
.expect("Verb_attr find");
let route = Route {
path: Some(verb_attr.tokens.clone().into()),
verb: Some({
let verb = match verb_attr
.path
.segments
.first()
.unwrap()
.ident
.to_string()
.as_str()
{
"get" => "warp::get()",
"post" => "warp::post()",
"put" => "warp::put()",
_ => unimplemented!("method wasn't implemented!"),
};
syn::parse_str(verb).unwrap()
}),
body,
query,
filter, //param: params.into_iter().next(),
};
let kwargs: Vec<syn::Ident> = {
// hack, substitute closure args with alphabet
let inputs = method.sig.inputs.clone();
let alphabet = vec!["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"];
inputs
.iter()
.enumerate()
.skip(1)
.map(|(i, _)| Ident::new(alphabet[i], Span::call_site()))
.collect()
};
let use_span = method.sig.output.clone();
//eprintln!("ENDPOINT---------------------");
let tokens = match route {
Route {
verb: Some(verb),
path: Some(path),
body,
query,
//param,
filter,
} => {
let mut setup = quote_spanned! {use_span.span() =>
let this = base.clone();
let this = this.and(warp::path!#path);
};
let end = quote_spanned! {use_span.span() =>
this
.and(#verb)
.and_then(|a: #struct_ty, header: Option<String>, #(#kwargs),*|{
async move {
let res = #static_method_call(&a, #(#kwargs),*).await;
let res = match header {
Some(content) => {
match content.as_str() {
r"application/json" => warp::reply::json(&res),
r"text/html" => {
eprintln!("No HTML yet");
unimplemented!("We dont have html yet")
},
_ => unimplemented!()
}
},
_ => unimplemented!()
};
std::result::Result::<_, warp::Rejection>::Ok(res)
}
})
.boxed()
};
if let Some(t) = filter {
setup = quote_spanned! {use_span.span() =>
#setup
let this = this.and(#t(&self.clone())).boxed();
}
}
if let Some(t) = query {
setup = quote_spanned! {use_span.span() =>
#setup
let this = this.and(warp::query::<#t>()).boxed();
}
}
if let Some(t) = body {
setup = quote_spanned! {use_span.span() =>
#setup
let this = this.and(warp::body::json::<#t>()).boxed();
}
}
quote_spanned! {use_span.span() =>
{
// setup
#setup
// ending
#end
// return this
}
}
}
_ => panic!("Verb and path need defined for route to be valid"),
};
tokens
}
fn extract_cors_ident(middlewares: syn::Expr) -> Option<syn::Ident> {
if let syn::Expr::Paren(expr) = middlewares.clone() {
if let syn::Expr::Call(call) = *expr.expr {
if let syn::Expr::Path(expr_path) = *call.func {
if match expr_path
.path
.segments
.last()
.map(|segment| segment.ident.to_string())
{
Some(string) if string == "cors".to_string() => true,
_ => false,
} {
if let syn::Expr::Path(opt_path) =
call.args.first().expect("Must have first here")
{
return Some(
opt_path
.path
.segments
.first()
.cloned()
.expect("Here , too!")
.ident,
);
}
//if let syn::Expr::Path(expr_path) = call.args {
//}
}
}
}
}
return None;
}
fn create_route_impl(controller_impl: syn::ItemImpl) -> TokenStream {
let struct_ty = controller_impl.self_ty.clone();
let routes: Vec<syn::export::TokenStream2> = controller_impl
.items
.iter()
.filter_map(|item| match item {
// can impls have non methods?
syn::ImplItem::Method(method) => Some(create_route(&*struct_ty, method)),
_ => None,
})
.collect();
// middleware/cors
let middleware = controller_impl.attrs.iter().find(|attr| {
match attr
.path
.segments
.last()
.map(|segment| segment.ident.to_string())
{
Some(string) if string == "middleware".to_string() => true,
_ => false,
}
});
let ctrl_filters = controller_impl
.attrs
.iter()
.find(|attr| {
match attr
.path
.segments
.last()
.map(|segment| segment.ident.to_string())
{
Some(string) if string == "filter".to_string() => true,
_ => false,
}
})
.map(|attr| {
//has to be either Tuple(ExprTuple or Paren(ExprParen(
//
//
fn find1(expr: syn::Expr) -> Option<syn::Ident> {
if let syn::Expr::Path(syn::ExprPath { path, .. }) = expr {
path.segments.last().map(|segment| segment.ident.clone())
} else {
None
}
}
let expr = syn::parse::<syn::Expr>(attr.clone().tokens.into()).unwrap();
match expr {
syn::Expr::Tuple(syn::ExprTuple { elems, .. }) => {
let names: Vec<syn::Ident> = elems.into_iter().filter_map(find1).collect();
names
}
syn::Expr::Paren(syn::ExprParen { expr, .. }) => {
find1(*expr.clone()).map(|n| vec![n]).unwrap_or(Vec::new())
}
_ => panic!("This usage not supported!"),
}
})
.unwrap_or(Vec::new());
let cors_options: Option<syn::Ident> = {
match middleware {
Some(attr) => {
let middlewares: syn::Expr =
syn::parse(attr.tokens.clone().into()).expect("Middleware has values..");
extract_cors_ident(middlewares)
}
_ => None,
}
};
let (first, rest) = routes.split_first().unwrap();
// defines the impl with fn routes
let impl_routes = {
let generics = controller_impl.generics.clone();
let self_ty = controller_impl.self_ty.clone();
match cors_options {
Some(ident) => {
quote! {
impl #generics #self_ty {
pub fn routes(self) -> impl warp::Filter<Extract=impl warp::Reply, Error=warp::Rejection> + Clone {
use warp::Filter;
use warp::cors::Builder;
let this = self.clone();
let this = warp::any()
.map(move || this.clone());
let this = this.and(warp::header::optional::<String>("accept"));
let this = this#(.and(#ctrl_filters(&self)))*;
let base = this.clone().boxed();
let builder: warp::cors::Cors = #ident();
let routes = #first#( .or(#rest) )*;
routes.with(builder)
}
}
#controller_impl
}
}
None => {
quote! {
impl #generics #self_ty {
pub fn routes(self) -> impl warp::Filter<Extract=impl warp::Reply, Error=warp::Rejection> + Clone {
use warp::Filter;
let this = self.clone();
let this = warp::any()
.map(move || this.clone());
let this = this.and(warp::header::optional::<String>("accept"));
let this = this#(.and(#ctrl_filters(&self)))*;
let base = this.clone().boxed();
#first#( .or(#rest) )*
}
}
#controller_impl
}
}
}
};
impl_routes.into()
}
#[proc_macro_attribute]
pub fn routes(meta: TokenStream, input: TokenStream) -> TokenStream {
let item: syn::Item = syn::parse(input).expect("failed to parse input.. ");
match item {
syn::Item::Impl(impl_item) => create_route_impl(impl_item),
_ => unimplemented!(),
}
}
#[proc_macro_attribute]
pub fn put(_metadata: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn query(_metadata: TokenStream, input: TokenStream) -> TokenStream {
input
}
#[proc_macro_attribute]
pub fn get(_metadata: TokenStream, input: TokenStream) -> TokenStream {
// Parse the `TokenStream` into a syntax tree, specifically an `Item`. An `Item` is a
// syntax item that can appear at the module level i.e. a function definition, a struct
// or enum definition, etc.
let item: syn::Item = syn::parse(input).expect("failed to parse input");
match item {
syn::Item::Fn(func) => {
// Use `quote` to convert the syntax tree back into tokens so we can return them. Note
// that the tokens we're returning at this point are still just the input, we've simply
// converted it between a few different forms.
let output = quote! { #func };
output.into()
}
_ => {
// This is how you generate a compiler error. You can also
// generate a "note," or a "warning."
// item.span()
// .unstable()
// .error("Expected #[get] to be used on a function")
// .emit();
unimplemented!()
}
}
}
#[proc_macro_attribute]
pub fn post(_metadata: TokenStream, input: TokenStream) -> TokenStream {
// Parse the `TokenStream` into a syntax tree, specifically an `Item`. An `Item` is a
// syntax item that can appear at the module level i.e. a function definition, a struct
// or enum definition, etc.
let item: syn::Item = syn::parse(input).expect("failed to parse input");
match item {
syn::Item::Fn(func) => {
// Use `quote` to convert the syntax tree back into tokens so we can return them. Note
// that the tokens we're returning at this point are still just the input, we've simply
// converted it between a few different forms.
let output = quote! { #func };
output.into()
}
_ => {
// This is how you generate a compiler error. You can also
// generate a "note," or a "warning."
// item.span()
// .unstable()
// .error("Expected #[get] to be used on a function")
// .emit();
unimplemented!()
}
}
}
#[proc_macro_attribute]
pub fn body(_metadata: TokenStream, input: TokenStream) -> TokenStream {
// Parse the `TokenStream` into a syntax tree, specifically an `Item`. An `Item` is a
// syntax item that can appear at the module level i.e. a function definition, a struct
// or enum definition, etc.
let item: syn::Item = syn::parse(input).expect("failed to parse input");
match item {
syn::Item::Fn(func) => {
// Use `quote` to convert the syntax tree back into tokens so we can return them. Note
// that the tokens we're returning at this point are still just the input, we've simply
// converted it between a few different forms.
let output = quote! { #func };
output.into()
}
_ => {
// This is how you generate a compiler error. You can also
// generate a "note," or a "warning."
// item.span()
// .unstable()
// .error("Expected #[get] to be used on a function")
// .emit();
unimplemented!()
}
}
}
#[proc_macro_attribute]
pub fn filter(_metadata: TokenStream, input: TokenStream) -> TokenStream {
// Parse the `TokenStream` into a syntax tree, specifically an `Item`. An `Item` is a
// syntax item that can appear at the module level i.e. a function definition, a struct
// or enum definition, etc.
//let item: syn::Item = syn::parse(input).expect("failed to parse input");
//match item {
// syn::Item::Fn(func) => {
// let output: syn::Type = match func.clone().sig.output {
// syn::ReturnType::Default => panic!("Unit extract not supported yet"),
// syn::ReturnType::Type(.., of) => (*of.clone()),
// };
// let name = func.clone().sig.ident;
// // Use `quote` to convert the syntax tree back into tokens so we can return them. Note
// // that the tokens we're returning at this point are still just the input, we've simply
// // converted it between a few different forms.
// let output = quote! {
// use in_space::Middleware;
// enum #name {}
// impl Middleware for #name {
// type Output = #output;
// }
// #func
// };
// let token_stream: TokenStream = output.into();
// println!("{:?}", token_stream.to_string());
// token_stream
// }
// _ => {
// // This is how you generate a compiler error. You can also
// // generate a "note," or a "warning."
// // item.span()
// // .unstable()
// // .error("Expected #[get] to be used on a function")
// // .emit();
// unimplemented!()
// }
//}
//
input
}
#[proc_macro_attribute]
pub fn middleware(_metadata: TokenStream, input: TokenStream) -> TokenStream {
// Parse the `TokenStream` into a syntax tree, specifically an `Item`. An `Item` is a
// syntax item that can appear at the module level i.e. a function definition, a struct
// or enum definition, etc.
let item: syn::Item = syn::parse(input).expect("failed to parse input");
match item {
syn::Item::Impl(func) => {
// Use `quote` to convert the syntax tree back into tokens so we can return them. Note
// that the tokens we're returning at this point are still just the input, we've simply
// converted it between a few different forms.
let output = quote! { #func };
output.into()
}
_ => {
// This is how you generate a compiler error. You can also
// generate a "note," or a "warning."
// item.span()
// .unstable()
// .error("Expected #[get] to be used on a function")
// .emit();
unimplemented!()
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}