in_space_routes 0.0.5

Routing macros for `in_space`
Documentation
#![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);
    }
}