1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#![feature(proc_macro_span)]
#![feature(type_alias_impl_trait)]
#![feature(proc_macro_diagnostic)]
#![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::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{parse_macro_input, token, Expr, Ident, Token, Type, Visibility};

fn create_route_impl(controller_impl: syn::ItemImpl) -> TokenStream {
    let struct_name = match *controller_impl.clone().self_ty {
        syn::Type::Path(type_path) => type_path.path.segments.first().unwrap().ident.to_string(),
        _ => unimplemented!(),
    };

    struct Route {
        path: TokenStream,
        verb: String,
        call: syn::Expr,
    }

    let routes: Vec<syn::export::TokenStream2> = controller_impl
        .items
        .iter()
        .filter_map(|item| {
            match item {
                syn::ImplItem::Method(method) => {
                    let method_name = method.sig.ident.to_string();

                    let call: syn::Expr =
                        syn::parse_str(format!("{}::{}", struct_name, method_name).as_str())
                            .unwrap();

                    //eprintln!("Route handler registered as {:?}", call);

                    let attr = method.attrs.clone().into_iter().next().unwrap();

                    // e.g. ("job" / u64)
                    let path: syn::export::TokenStream2 = attr.tokens.clone().into(); //.span().unwrap().source_text().unwrap();

                    let verb = attr.path.segments.first().unwrap().ident.to_string();

                    let get: syn::Expr = syn::parse_str("warp::get()").unwrap();

                    let ret = method.sig.output.clone();

                    let r = quote_spanned! {ret.span()=>
                        {
                            let this = this.clone();
                            warp::any()
                                .map(move || this.clone()) // huh.. how we gonna do this...
                                .and(warp::path!#path)
                                .and(#get)
                                .and_then(#call)
                        }
                    };

                    Some(r)
                }

                _ => None,
            }
        })
        .collect();

    let struct_ty: syn::TypePath = syn::parse_str(struct_name.as_str()).expect("struct name");

    let impl_routes = quote! {
            impl #struct_ty {
                fn routes(self) -> impl warp::Filter<Extract=impl warp::Reply, Error=warp::Rejection> + Clone {
                    let dummy = warp::path!("thisshouldnevermatch")
                                .map(|| warp::reply());

                    let this = self;

                    dummy#( .or(#routes) )*
                }
            }

            #controller_impl
    };

    //    eprintln!("impl_routes: {:?}", impl_routes.to_string());

    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 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!()
        }
    }
}

#[cfg(test)]
mod tests {

    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}