use std::iter::{self, Once};
use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
use proc_macro_error::{abort, abort_call_site, proc_macro_error};
#[proc_macro]
#[proc_macro_error]
pub fn get(path: TokenStream) -> TokenStream {
let method: TokenStream = "&::solarsail::http::Method::GET".parse().unwrap();
method_macro(method, path)
}
#[proc_macro]
#[proc_macro_error]
pub fn head(path: TokenStream) -> TokenStream {
let method: TokenStream = "&::solarsail::http::Method::HEAD".parse().unwrap();
method_macro(method, path)
}
#[proc_macro]
#[proc_macro_error]
pub fn post(path: TokenStream) -> TokenStream {
let method: TokenStream = "&::solarsail::http::Method::POST".parse().unwrap();
method_macro(method, path)
}
#[proc_macro]
#[proc_macro_error]
pub fn put(path: TokenStream) -> TokenStream {
let method: TokenStream = "&::solarsail::http::Method::PUT".parse().unwrap();
method_macro(method, path)
}
#[proc_macro]
#[proc_macro_error]
pub fn delete(path: TokenStream) -> TokenStream {
let method: TokenStream = "&::solarsail::http::Method::DELETE".parse().unwrap();
method_macro(method, path)
}
#[proc_macro]
#[proc_macro_error]
pub fn patch(path: TokenStream) -> TokenStream {
let method: TokenStream = "&::solarsail::http::Method::PATCH".parse().unwrap();
method_macro(method, path)
}
#[proc_macro]
#[proc_macro_error]
pub fn route(path: TokenStream) -> TokenStream {
let mut path = path.into_iter();
let method = (&mut path)
.take_while(|token| {
match token {
TokenTree::Punct(p) => {
match p.as_char() {
',' => false,
'/' => abort_call_site!(
"route! macro must contain a pattern matching expected methods as its first argument"
),
_ => true
}
}
_ => true
}
})
.collect();
method_macro(method, path.collect())
}
fn method_macro(method: TokenStream, path: TokenStream) -> TokenStream {
let mut expect_slash = false;
let path: TokenStream = if path.is_empty() {
iter::once(TokenTree::Ident(Ident::new("_", Span::call_site()))).collect()
} else {
punct_once('&')
.chain(iter::once(TokenTree::Group(Group::new(
Delimiter::Bracket,
path.into_iter()
.enumerate()
.filter_map(|(i, item)| {
Some(match item {
TokenTree::Group(g) => abort!(g.span(), "unexpected group"),
item @ TokenTree::Ident(_) => {
expect_slash = true;
item
}
TokenTree::Punct(p) if p.as_char() == '/' => {
if i == 0 {
return None;
}
if !expect_slash {
abort!(p.span(), "unexpected forward slash")
}
expect_slash = false;
TokenTree::Punct(Punct::new(',', Spacing::Alone))
}
TokenTree::Punct(p) => {
if p.spacing() == Spacing::Alone {
expect_slash = true;
}
TokenTree::Punct(p)
}
literal @ TokenTree::Literal(_) => {
expect_slash = true;
literal
}
})
})
.collect(),
))))
.collect()
};
let result: TokenStream = TokenTree::Group(Group::new(
Delimiter::Parenthesis,
method
.into_iter()
.chain(punct_once(','))
.chain(path)
.collect(),
))
.into();
result
}
fn punct_once(ch: char) -> Once<TokenTree> {
iter::once(TokenTree::Punct(Punct::new(ch, Spacing::Alone)))
}