From a6ff054ba3dedc8a42f72ad0de616c04b4155dd6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 27 Nov 2020 21:44:59 +0100 Subject: [PATCH] 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 --- ruma-api-macros/src/api/metadata.rs | 159 ++++++++++++++++++---------- 1 file changed, 105 insertions(+), 54 deletions(-) diff --git a/ruma-api-macros/src/api/metadata.rs b/ruma-api-macros/src/api/metadata.rs index e998651e..a6df2bc8 100644 --- a/ruma-api-macros/src/api/metadata.rs +++ b/ruma-api-macros/src/api/metadata.rs @@ -1,15 +1,22 @@ //! Details of the `metadata` section of the procedural macro. +use quote::ToTokens; use syn::{ braced, parse::{Parse, ParseStream}, - Expr, ExprLit, ExprPath, FieldValue, Ident, Lit, LitBool, LitStr, Member, Token, + Ident, LitBool, LitStr, Token, }; use crate::util; mod kw { 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. @@ -33,10 +40,25 @@ pub struct Metadata { pub authentication: Ident, } +fn set_field(field: &mut Option, 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 { fn parse(input: ParseStream<'_>) -> syn::Result { - let metadata_kw = input.parse::()?; - input.parse::()?; + let metadata_kw: kw::metadata = input.parse()?; + let _: Token![:] = input.parse()?; + let field_values; braced!(field_values in input); @@ -51,57 +73,13 @@ impl Parse for Metadata { let mut authentication = None; for field_value in field_values { - let identifier = match field_value.member.clone() { - Member::Named(identifier) => identifier, - _ => panic!("expected Member::Named"), - }; - let expr = field_value.expr.clone(); - - match &identifier.to_string()[..] { - "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")), + match field_value { + FieldValue::Description(d) => set_field(&mut description, d)?, + FieldValue::Method(m) => set_field(&mut method, m)?, + FieldValue::Name(n) => set_field(&mut name, n)?, + FieldValue::Path(p) => set_field(&mut path, p)?, + FieldValue::RateLimited(rl) => set_field(&mut rate_limited, rl)?, + FieldValue::Authentication(a) => set_field(&mut authentication, a)?, } } @@ -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 { + 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 { + 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()?), + }) + } +}