plaster_router_macro/
lib.rs1extern crate proc_macro;
2#[macro_use]
3extern crate quote;
4
5use proc_macro::TokenStream;
6
7#[proc_macro_derive(Routes, attributes(route))]
8pub fn plaster_router(input: TokenStream) -> TokenStream {
9 match syn::parse2::<syn::Item>(input.into()) {
10 Ok(item) => match item {
11 syn::Item::Enum(item_enum) => parse_enum(item_enum).into(),
12 _ => panic!("plaster_router must be used on an enum"),
13 },
14 Err(e) => {
15 panic!("parse error: {}", e);
16 }
17 }
18}
19
20fn parse_enum(item: syn::ItemEnum) -> proc_macro2::TokenStream {
21 let ident = item.ident;
22 let routes = item.variants.into_iter().map(|variant| {
23 if let Some(path) = parse_route_attr(&variant.attrs) {
24 let mut route = path.as_str();
25 if route.len() != 0 && route.as_bytes()[0] == b'/' {
26 route = &route[1..];
27 }
28
29 let route_literal = syn::LitStr::new(route, proc_macro2::Span::call_site());
30 let variant_ident = variant.ident;
31 let mut params = Vec::new();
32
33 for segment in route.split('/') {
34 if segment.len() > 0 && segment.as_bytes()[0] == b':' {
35 params.push(segment[1..].to_string());
36 } else if segment.len() > 0 && segment.as_bytes()[0] == b'*' {
37 params.push(segment[1..].to_string());
38 }
39 }
40
41 if params.len() > 0 {
42 if let syn::Fields::Named(fields) = variant.fields {
43 let field_idents: Vec<_> = fields
57 .named
58 .into_iter()
59 .map(|field| field.ident.unwrap())
60 .collect();
61 let params_literal: Vec<syn::LitStr> = params
62 .iter()
63 .map(|param| syn::LitStr::new(param, proc_macro2::Span::call_site()))
64 .collect();
65
66 quote! {
67 router.add_route(#route_literal, |params| {
68 #ident::#variant_ident {
69 #(
70 #field_idents: params.find(#params_literal).unwrap().to_string()
71 ),*
72 }
73 });
74 }
75 } else {
76 panic!("all variants with params must have named fields");
77 }
78 } else {
79 quote! {
80 router.add_route(#route_literal, |_| #ident::#variant_ident);
81 }
82 }
83 } else {
84 panic!("all variants of the enum must have a route attribute");
85 }
86 });
87
88 quote! {
89 impl plaster_router::Routes<#ident> for #ident {
90 fn router(callback: plaster::callback::Callback<()>) -> plaster_router::Router<#ident> {
91 let mut router = plaster_router::Router::new(callback);
92 #(#routes)*
93 router
94 }
95 }
96 }
97}
98
99fn parse_route_attr(attrs: &[syn::Attribute]) -> Option<String> {
100 attrs.iter().find_map(|attr| {
101 let meta = attr
102 .parse_meta()
103 .expect("could not parse meta for attribute");
104 match meta {
105 syn::Meta::List(list) => {
106 if list.ident == "route" {
107 if let Some(route) = list.nested.first() {
108 if let syn::NestedMeta::Literal(syn::Lit::Str(route)) = route.value() {
109 Some(route.value())
110 } else {
111 panic!("route spec in route attribute must be a string in quotes");
112 }
113 } else {
114 panic!("must specify a route spec in route attribute");
115 }
116 } else {
117 None
118 }
119 }
120 _ => None,
121 }
122 })
123}