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:
parent
761aecbe4e
commit
a6ff054ba3
@ -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()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user