Handle repeated HTTP Method edge case

Refactor code to detect when the same HTTP method is specified more than
once. eg. `#[route("/multi", method = "GET", method = "GET")]`
and return a compile time error.
This commit is contained in:
Matt Gathu 2020-09-13 11:59:02 +02:00
parent 90a6b47927
commit 8bb205846d
1 changed files with 32 additions and 17 deletions

View File

@ -1,5 +1,7 @@
extern crate proc_macro; extern crate proc_macro;
use std::collections::HashSet;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use quote::{format_ident, quote, ToTokens, TokenStreamExt};
@ -17,7 +19,7 @@ impl ToTokens for ResourceType {
} }
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Eq, Hash)]
pub enum GuardType { pub enum GuardType {
Get, Get,
Post, Post,
@ -59,7 +61,7 @@ struct Args {
path: syn::LitStr, path: syn::LitStr,
guards: Vec<Ident>, guards: Vec<Ident>,
wrappers: Vec<syn::Type>, wrappers: Vec<syn::Type>,
methods: Vec<GuardType>, methods: HashSet<GuardType>,
} }
impl Args { impl Args {
@ -67,7 +69,7 @@ impl Args {
let mut path = None; let mut path = None;
let mut guards = Vec::new(); let mut guards = Vec::new();
let mut wrappers = Vec::new(); let mut wrappers = Vec::new();
let mut methods = Vec::new(); let mut methods = HashSet::new();
for arg in args { for arg in args {
match arg { match arg {
NestedMeta::Lit(syn::Lit::Str(lit)) => match path { NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
@ -102,26 +104,38 @@ impl Args {
} }
} else if nv.path.is_ident("method") { } else if nv.path.is_ident("method") {
if let syn::Lit::Str(ref lit) = nv.lit { if let syn::Lit::Str(ref lit) = nv.lit {
let guard_type: Option<GuardType> =
match lit.value().as_str() { match lit.value().as_str() {
"CONNECT" => methods.push(GuardType::Connect), "CONNECT" => Some(GuardType::Connect),
"DELETE" => methods.push(GuardType::Delete), "DELETE" => Some(GuardType::Delete),
"GET" => methods.push(GuardType::Get), "GET" => Some(GuardType::Get),
"HEAD" => methods.push(GuardType::Head), "HEAD" => Some(GuardType::Head),
"OPTIONS" => methods.push(GuardType::Options), "OPTIONS" => Some(GuardType::Options),
"PATCH" => methods.push(GuardType::Patch), "PATCH" => Some(GuardType::Patch),
"POST" => methods.push(GuardType::Post), "POST" => Some(GuardType::Post),
"PUT" => methods.push(GuardType::Put), "PUT" => Some(GuardType::Put),
"TRACE" => methods.push(GuardType::Trace), "TRACE" => Some(GuardType::Trace),
_ => { _ => None,
};
if let Some(guard) = guard_type {
if !methods.insert(guard) {
return Err(syn::Error::new_spanned(
&nv.lit,
&format!(
"HTTP Method defined more than once: `{}`",
lit.value()
),
));
}
} else {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
&nv.lit, &nv.lit,
&format!( &format!(
"Unexpected HTTP Method: `{}`", "Unexpected HTTP Method: `{}`",
lit.value() lit.value()
), ),
)) ));
} }
};
} else { } else {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
nv.lit, nv.lit,
@ -246,6 +260,7 @@ impl ToTokens for Route {
resource_type, resource_type,
} = self; } = self;
let resource_name = name.to_string(); let resource_name = name.to_string();
let methods = methods.iter();
let guard_gen = if guard == &GuardType::Multi { let guard_gen = if guard == &GuardType::Multi {
quote! { quote! {