solarsail-macros 0.4.0

macros for the solarsail crate
Documentation
use std::iter::{self, Once};

use proc_macro::{Delimiter, Group, Punct, Spacing, 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 = 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)))
}