api-macros: Refactor metadata parsing

* duplicate field assignment will now raise an error
* parsing should now be faster (though it probably doesn't matter)
* the code is now split into more independent parts
This commit is contained in:
Jonas Platte 2020-11-27 21:44:59 +01:00
parent 761aecbe4e
commit a6ff054ba3
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67

View File

@ -1,15 +1,22 @@
//! Details of the `metadata` section of the procedural macro. //! Details of the `metadata` section of the procedural macro.
use quote::ToTokens;
use syn::{ use syn::{
braced, braced,
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
Expr, ExprLit, ExprPath, FieldValue, Ident, Lit, LitBool, LitStr, Member, Token, Ident, LitBool, LitStr, Token,
}; };
use crate::util; use crate::util;
mod kw { mod kw {
syn::custom_keyword!(metadata); syn::custom_keyword!(metadata);
syn::custom_keyword!(description);
syn::custom_keyword!(method);
syn::custom_keyword!(name);
syn::custom_keyword!(path);
syn::custom_keyword!(rate_limited);
syn::custom_keyword!(authentication);
} }
/// The result of processing the `metadata` section of the macro. /// The result of processing the `metadata` section of the macro.
@ -33,10 +40,25 @@ pub struct Metadata {
pub authentication: Ident, pub authentication: Ident,
} }
fn set_field<T: ToTokens>(field: &mut Option<T>, value: T) -> syn::Result<()> {
match field {
Some(existing_value) => {
let mut error = syn::Error::new_spanned(value, "duplicate field assignment");
error.combine(syn::Error::new_spanned(existing_value, "first one here"));
Err(error)
}
None => {
*field = Some(value);
Ok(())
}
}
}
impl Parse for Metadata { impl Parse for Metadata {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> { fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let metadata_kw = input.parse::<kw::metadata>()?; let metadata_kw: kw::metadata = input.parse()?;
input.parse::<Token![:]>()?; let _: Token![:] = input.parse()?;
let field_values; let field_values;
braced!(field_values in input); braced!(field_values in input);
@ -51,57 +73,13 @@ impl Parse for Metadata {
let mut authentication = None; let mut authentication = None;
for field_value in field_values { for field_value in field_values {
let identifier = match field_value.member.clone() { match field_value {
Member::Named(identifier) => identifier, FieldValue::Description(d) => set_field(&mut description, d)?,
_ => panic!("expected Member::Named"), FieldValue::Method(m) => set_field(&mut method, m)?,
}; FieldValue::Name(n) => set_field(&mut name, n)?,
let expr = field_value.expr.clone(); FieldValue::Path(p) => set_field(&mut path, p)?,
FieldValue::RateLimited(rl) => set_field(&mut rate_limited, rl)?,
match &identifier.to_string()[..] { FieldValue::Authentication(a) => set_field(&mut authentication, a)?,
"description" => match expr {
Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }) => {
description = Some(literal);
}
_ => return Err(syn::Error::new_spanned(expr, "expected a string literal")),
},
"method" => match expr {
Expr::Path(ExprPath { ref path, .. }) if path.segments.len() == 1 => {
method = Some(path.segments[0].ident.clone());
}
_ => return Err(syn::Error::new_spanned(expr, "expected an identifier")),
},
"name" => match expr {
Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }) => {
name = Some(literal);
}
_ => return Err(syn::Error::new_spanned(expr, "expected a string literal")),
},
"path" => match expr {
Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }) => {
let path_str = literal.value();
if !util::is_valid_endpoint_path(&path_str) {
return Err(syn::Error::new_spanned(
literal,
"path may only contain printable ASCII characters with no spaces",
));
}
path = Some(literal);
}
_ => return Err(syn::Error::new_spanned(expr, "expected a string literal")),
},
"rate_limited" => match expr {
Expr::Lit(ExprLit { lit: Lit::Bool(literal), .. }) => {
rate_limited = Some(literal);
}
_ => return Err(syn::Error::new_spanned(expr, "expected a bool literal")),
},
"authentication" => match expr {
Expr::Path(ExprPath { ref path, .. }) if path.segments.len() == 1 => {
authentication = Some(path.segments[0].ident.clone());
}
_ => return Err(syn::Error::new_spanned(expr, "expected an identifier")),
},
_ => return Err(syn::Error::new_spanned(field_value, "unexpected field")),
} }
} }
@ -118,3 +96,76 @@ impl Parse for Metadata {
}) })
} }
} }
enum Field {
Description,
Method,
Name,
Path,
RateLimited,
Authentication,
}
impl Parse for Field {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::description) {
let _: kw::description = input.parse()?;
Ok(Self::Description)
} else if lookahead.peek(kw::method) {
let _: kw::method = input.parse()?;
Ok(Self::Method)
} else if lookahead.peek(kw::name) {
let _: kw::name = input.parse()?;
Ok(Self::Name)
} else if lookahead.peek(kw::path) {
let _: kw::path = input.parse()?;
Ok(Self::Path)
} else if lookahead.peek(kw::rate_limited) {
let _: kw::rate_limited = input.parse()?;
Ok(Self::RateLimited)
} else if lookahead.peek(kw::authentication) {
let _: kw::authentication = input.parse()?;
Ok(Self::Authentication)
} else {
Err(lookahead.error())
}
}
}
enum FieldValue {
Description(LitStr),
Method(Ident),
Name(LitStr),
Path(LitStr),
RateLimited(LitBool),
Authentication(Ident),
}
impl Parse for FieldValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
let field: Field = input.parse()?;
let _: Token![:] = input.parse()?;
Ok(match field {
Field::Description => Self::Description(input.parse()?),
Field::Method => Self::Method(input.parse()?),
Field::Name => Self::Name(input.parse()?),
Field::Path => {
let path: LitStr = input.parse()?;
if !util::is_valid_endpoint_path(&path.value()) {
return Err(syn::Error::new_spanned(
&path,
"path may only contain printable ASCII characters with no spaces",
));
}
Self::Path(path)
}
Field::RateLimited => Self::RateLimited(input.parse()?),
Field::Authentication => Self::Authentication(input.parse()?),
})
}
}