use resource::{Arg, Attributes, Catch, Signature, Route, Resource};
use proc_macro2::TokenStream;
use syn;
pub struct Parse {
resources: Vec<Resource>,
}
struct ImplWeb {
resources: Vec<Resource>,
}
impl Parse {
pub fn parse(input: TokenStream) -> Parse {
let ast = syn::parse2(input).unwrap();
let mut v = ImplWeb::new();
syn::fold::fold_file(&mut v, ast);
for resource in &mut v.resources {
resource.finalize();
}
Parse {
resources: v.resources,
}
}
pub fn generate(&self) -> TokenStream {
let impl_resources = self.resources.iter()
.map(|resource| resource.gen());
quote! {
#(#impl_resources)*
}
}
}
impl ImplWeb {
fn new() -> ImplWeb {
ImplWeb {
resources: vec![],
}
}
fn resource(&mut self) -> &mut Resource {
self.resources.last_mut()
.expect("no resources defined")
}
}
impl syn::fold::Fold for ImplWeb {
fn fold_item_impl(&mut self, item: syn::ItemImpl) -> syn::ItemImpl {
assert!(
item.trait_.is_none(),
"trait impls must not be in impl_web! block"
);
for param in &item.generics.params {
use syn::GenericParam::Type;
match *param {
Type(_) => {}
ref actual => {
panic!("Resources may only have generic type parameters. Actual = {:?}", actual);
}
}
}
let index = self.resources.len();
self.resources.push(Resource::new(index, &item));
syn::fold::fold_item_impl(self, item)
}
fn fold_impl_item_method(&mut self, mut item: syn::ImplItemMethod) -> syn::ImplItemMethod {
use syn::ReturnType;
let mut attributes = Attributes::new();
item.attrs.retain(|attr| !attributes.process(attr));
if attributes.is_empty() {
return item;
}
let ident = item.sig.ident.clone();
let is_async = item.sig.asyncness.is_some();
let ret = match item.sig.decl.output {
ReturnType::Type(_, ref ty) => (**ty).clone(),
ReturnType::Default => syn::parse_str("()").unwrap(),
};
let mut args = vec![];
for arg in item.sig.decl.inputs.iter().skip(1) {
use syn::{FnArg, Pat};
match arg {
FnArg::Captured(arg) => {
let index = args.len();
match arg.pat {
Pat::Ident(ref ident) => {
let ident = ident.ident.to_string();
let capture = attributes.path_captures.iter()
.position(|capture| capture == &ident);
args.push(Arg::new(index, ident, capture, arg.ty.clone()));
}
_ => {
args.push(Arg::ty_only(index, arg.ty.clone()));
}
}
}
_ => panic!("unexpected fn argument type = {:?}", arg),
}
}
let sig = Signature::new(ident, ret, args, is_async);
let resource = self.resource();
let index = resource.routes.len();
if attributes.is_route() {
if let Some(_) = resource.routes.iter().map(|r| &r.attributes)
.position(|a| a.path == attributes.path &&
a.method == attributes.method &&
a.content_type == attributes.content_type) {
panic!("duplicate routes with method {:?}, path {:?}, content type {:?}:\n{:?}",
attributes.method, attributes.path, attributes.content_type, item.sig.ident);
}
let route = Route::new(index, sig, attributes);
resource.routes.push(route);
} else {
let catch = Catch::new(index, sig, attributes);
resource.catches.push(catch);
}
item
}
}