macros: Refactor ruma_api attributes parsing

This commit is contained in:
Kévin Commaille 2022-05-23 21:51:47 +02:00 committed by GitHub
parent 72fc21c342
commit 734770d2bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 197 additions and 222 deletions

View File

@ -2,77 +2,147 @@
use syn::{
parse::{Parse, ParseStream},
Ident, Lit, Token, Type,
Ident, LitStr, Token, Type,
};
/// Value type used for request and response struct attributes
mod kw {
syn::custom_keyword!(body);
syn::custom_keyword!(raw_body);
syn::custom_keyword!(path);
syn::custom_keyword!(query);
syn::custom_keyword!(query_map);
syn::custom_keyword!(header);
syn::custom_keyword!(authentication);
syn::custom_keyword!(method);
syn::custom_keyword!(error_ty);
syn::custom_keyword!(unstable);
syn::custom_keyword!(r0);
syn::custom_keyword!(stable);
syn::custom_keyword!(manual_body_serde);
}
pub enum RequestMeta {
NewtypeBody,
RawBody,
Path,
Query,
QueryMap,
Header(Ident),
}
impl Parse for RequestMeta {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::body) {
let _: kw::body = input.parse()?;
Ok(Self::NewtypeBody)
} else if lookahead.peek(kw::raw_body) {
let _: kw::raw_body = input.parse()?;
Ok(Self::RawBody)
} else if lookahead.peek(kw::path) {
let _: kw::path = input.parse()?;
Ok(Self::Path)
} else if lookahead.peek(kw::query) {
let _: kw::query = input.parse()?;
Ok(Self::Query)
} else if lookahead.peek(kw::query_map) {
let _: kw::query_map = input.parse()?;
Ok(Self::QueryMap)
} else if lookahead.peek(kw::header) {
let _: kw::header = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(Self::Header)
} else {
Err(lookahead.error())
}
}
}
pub enum DeriveRequestMeta {
Authentication(Type),
Method(Type),
ErrorTy(Type),
UnstablePath(LitStr),
R0Path(LitStr),
StablePath(LitStr),
}
impl Parse for DeriveRequestMeta {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::authentication) {
let _: kw::authentication = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(Self::Authentication)
} else if lookahead.peek(kw::method) {
let _: kw::method = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(Self::Method)
} else if lookahead.peek(kw::error_ty) {
let _: kw::error_ty = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(Self::ErrorTy)
} else if lookahead.peek(kw::unstable) {
let _: kw::unstable = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(Self::UnstablePath)
} else if lookahead.peek(kw::r0) {
let _: kw::r0 = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(Self::R0Path)
} else if lookahead.peek(kw::stable) {
let _: kw::stable = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(Self::StablePath)
} else {
Err(lookahead.error())
}
}
}
pub enum ResponseMeta {
NewtypeBody,
RawBody,
Header(Ident),
}
impl Parse for ResponseMeta {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::body) {
let _: kw::body = input.parse()?;
Ok(Self::NewtypeBody)
} else if lookahead.peek(kw::raw_body) {
let _: kw::raw_body = input.parse()?;
Ok(Self::RawBody)
} else if lookahead.peek(kw::header) {
let _: kw::header = input.parse()?;
let _: Token![=] = input.parse()?;
input.parse().map(Self::Header)
} else {
Err(lookahead.error())
}
}
}
#[allow(clippy::large_enum_variant)]
pub enum MetaValue {
Lit(Lit),
Type(Type),
pub enum DeriveResponseMeta {
ManualBodySerde,
ErrorTy(Type),
}
impl Parse for MetaValue {
impl Parse for DeriveResponseMeta {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
if input.peek(Lit) {
input.parse().map(Self::Lit)
} else {
input.parse().map(Self::Type)
}
}
}
/// Like syn::MetaNameValue, but expects an identifier as the value.
///
/// Also, we don't care about the the span of the equals sign, so we don't have the `eq_token` field
/// from syn::MetaNameValue.
pub struct MetaNameValue<V> {
/// The part left of the equals sign
pub name: Ident,
/// The part right of the equals sign
pub value: V,
}
impl<V: Parse> Parse for MetaNameValue<V> {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let ident = input.parse()?;
let lookahead = input.lookahead1();
if lookahead.peek(kw::manual_body_serde) {
let _: kw::manual_body_serde = input.parse()?;
Ok(Self::ManualBodySerde)
} else if lookahead.peek(kw::error_ty) {
let _: kw::error_ty = input.parse()?;
let _: Token![=] = input.parse()?;
Ok(MetaNameValue { name: ident, value: input.parse()? })
}
}
/// Like syn::Meta, but only parses ruma_api attributes
pub enum Meta<T> {
/// A single word, like `query` in `#[ruma_api(query)]`
Word(Ident),
/// A name-value pair, like `header = CONTENT_TYPE` in `#[ruma_api(header = CONTENT_TYPE)]`
NameValue(MetaNameValue<T>),
}
impl<T: Parse> Meta<T> {
/// Check if the given attribute is a ruma_api attribute.
///
/// If it is, parse it.
pub fn from_attribute(attr: &syn::Attribute) -> syn::Result<Option<Self>> {
if attr.path.is_ident("ruma_api") {
attr.parse_args().map(Some)
input.parse().map(Self::ErrorTy)
} else {
Ok(None)
}
}
}
impl<T: Parse> Parse for Meta<T> {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let ident = input.parse()?;
if input.peek(Token![=]) {
let _: Token![=] = input.parse()?;
Ok(Meta::NameValue(MetaNameValue { name: ident, value: input.parse()? }))
} else {
Ok(Meta::Word(ident))
Err(lookahead.error())
}
}
}

View File

@ -1,7 +1,6 @@
use std::{
collections::{BTreeMap, BTreeSet},
convert::{TryFrom, TryInto},
mem,
};
use proc_macro2::TokenStream;
@ -10,11 +9,11 @@ use syn::{
parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
DeriveInput, Field, Generics, Ident, Lifetime, Lit, LitStr, Token, Type,
DeriveInput, Field, Generics, Ident, Lifetime, LitStr, Token, Type,
};
use super::{
attribute::{Meta, MetaNameValue, MetaValue},
attribute::{DeriveRequestMeta, RequestMeta},
auth_scheme::AuthScheme,
util::collect_lifetime_idents,
};
@ -62,28 +61,16 @@ pub fn expand_derive_request(input: DeriveInput) -> syn::Result<TokenStream> {
continue;
}
let meta = attr.parse_args_with(Punctuated::<_, Token![,]>::parse_terminated)?;
for MetaNameValue { name, value } in meta {
match value {
MetaValue::Type(t) if name == "authentication" => {
authentication = Some(parse_quote!(#t));
}
MetaValue::Type(t) if name == "method" => {
method = Some(parse_quote!(#t));
}
MetaValue::Type(t) if name == "error_ty" => {
error_ty = Some(t);
}
MetaValue::Lit(Lit::Str(s)) if name == "unstable" => {
unstable_path = Some(s);
}
MetaValue::Lit(Lit::Str(s)) if name == "r0" => {
r0_path = Some(s);
}
MetaValue::Lit(Lit::Str(s)) if name == "stable" => {
stable_path = Some(s);
}
_ => unreachable!("invalid ruma_api({}) attribute", name),
let metas =
attr.parse_args_with(Punctuated::<DeriveRequestMeta, Token![,]>::parse_terminated)?;
for meta in metas {
match meta {
DeriveRequestMeta::Authentication(t) => authentication = Some(parse_quote!(#t)),
DeriveRequestMeta::Method(t) => method = Some(parse_quote!(#t)),
DeriveRequestMeta::ErrorTy(t) => error_ty = Some(t),
DeriveRequestMeta::UnstablePath(s) => unstable_path = Some(s),
DeriveRequestMeta::R0Path(s) => r0_path = Some(s),
DeriveRequestMeta::StablePath(s) => stable_path = Some(s),
}
}
}
@ -405,17 +392,18 @@ enum RequestField {
impl RequestField {
/// Creates a new `RequestField`.
fn new(kind: RequestFieldKind, field: Field, header: Option<Ident>) -> Self {
match kind {
RequestFieldKind::Body => RequestField::Body(field),
RequestFieldKind::Header => {
RequestField::Header(field, header.expect("missing header name"))
fn new(field: Field, kind_attr: Option<RequestMeta>) -> Self {
if let Some(attr) = kind_attr {
match attr {
RequestMeta::NewtypeBody => RequestField::NewtypeBody(field),
RequestMeta::RawBody => RequestField::RawBody(field),
RequestMeta::Path => RequestField::Path(field),
RequestMeta::Query => RequestField::Query(field),
RequestMeta::QueryMap => RequestField::QueryMap(field),
RequestMeta::Header(header) => RequestField::Header(field, header),
}
RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field),
RequestFieldKind::RawBody => RequestField::RawBody(field),
RequestFieldKind::Path => RequestField::Path(field),
RequestFieldKind::Query => RequestField::Query(field),
RequestFieldKind::QueryMap => RequestField::QueryMap(field),
} else {
RequestField::Body(field)
}
}
@ -477,55 +465,22 @@ impl TryFrom<Field> for RequestField {
type Error = syn::Error;
fn try_from(mut field: Field) -> syn::Result<Self> {
let mut field_kind = None;
let mut header = None;
let (mut api_attrs, attrs) =
field.attrs.into_iter().partition::<Vec<_>, _>(|attr| attr.path.is_ident("ruma_api"));
field.attrs = attrs;
for attr in mem::take(&mut field.attrs) {
let meta = match Meta::from_attribute(&attr)? {
Some(m) => m,
None => {
field.attrs.push(attr);
continue;
let kind_attr = match api_attrs.as_slice() {
[] => None,
[_] => Some(api_attrs.pop().unwrap().parse_args::<RequestMeta>()?),
_ => {
return Err(syn::Error::new_spanned(
&api_attrs[1],
"multiple field kind attribute found, there can only be one",
));
}
};
if field_kind.is_some() {
return Err(syn::Error::new_spanned(
attr,
"There can only be one field kind attribute",
));
}
field_kind = Some(match meta {
Meta::Word(ident) => match &ident.to_string()[..] {
"body" => RequestFieldKind::NewtypeBody,
"raw_body" => RequestFieldKind::RawBody,
"path" => RequestFieldKind::Path,
"query" => RequestFieldKind::Query,
"query_map" => RequestFieldKind::QueryMap,
_ => {
return Err(syn::Error::new_spanned(
ident,
"Invalid #[ruma_api] argument, expected one of \
`body`, `raw_body`, `path`, `query`, `query_map`",
));
}
},
Meta::NameValue(MetaNameValue { name, value }) => {
if name != "header" {
return Err(syn::Error::new_spanned(
name,
"Invalid #[ruma_api] argument with value, expected `header`",
));
}
header = Some(value);
RequestFieldKind::Header
}
});
}
Ok(RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header))
Ok(RequestField::new(field, kind_attr))
}
}
@ -540,15 +495,3 @@ impl ToTokens for RequestField {
self.field().to_tokens(tokens)
}
}
/// The types of fields that a request can have, without their values.
#[derive(Clone, Copy, PartialEq, Eq)]
enum RequestFieldKind {
Body,
Header,
NewtypeBody,
RawBody,
Path,
Query,
QueryMap,
}

View File

@ -1,6 +1,5 @@
use std::{
convert::{TryFrom, TryInto},
mem,
ops::Not,
};
@ -13,7 +12,7 @@ use syn::{
DeriveInput, Field, Generics, Ident, Lifetime, Token, Type,
};
use super::attribute::{Meta, MetaNameValue};
use super::attribute::{DeriveResponseMeta, ResponseMeta};
use crate::util::import_ruma_common;
mod incoming;
@ -33,18 +32,12 @@ pub fn expand_derive_response(input: DeriveInput) -> syn::Result<TokenStream> {
continue;
}
let metas = attr.parse_args_with(Punctuated::<Meta<Type>, Token![,]>::parse_terminated)?;
let metas =
attr.parse_args_with(Punctuated::<DeriveResponseMeta, Token![,]>::parse_terminated)?;
for meta in metas {
match meta {
Meta::Word(w) if w == "manual_body_serde" => {
manual_body_serde = true;
}
Meta::NameValue(MetaNameValue { name, value }) if name == "error_ty" => {
error_ty = Some(value);
}
Meta::Word(name) | Meta::NameValue(MetaNameValue { name, .. }) => {
unreachable!("invalid ruma_api({}) attribute", name);
}
DeriveResponseMeta::ManualBodySerde => manual_body_serde = true,
DeriveResponseMeta::ErrorTy(t) => error_ty = Some(t),
}
}
}
@ -180,6 +173,19 @@ enum ResponseField {
}
impl ResponseField {
/// Creates a new `ResponseField`.
fn new(field: Field, kind_attr: Option<ResponseMeta>) -> Self {
if let Some(attr) = kind_attr {
match attr {
ResponseMeta::NewtypeBody => ResponseField::NewtypeBody(field),
ResponseMeta::RawBody => ResponseField::RawBody(field),
ResponseMeta::Header(header) => ResponseField::Header(field, header),
}
} else {
ResponseField::Body(field)
}
}
/// Gets the inner `Field` value.
fn field(&self) -> &Field {
match self {
@ -226,58 +232,22 @@ impl TryFrom<Field> for ResponseField {
));
}
let mut field_kind = None;
let mut header = None;
let (mut api_attrs, attrs) =
field.attrs.into_iter().partition::<Vec<_>, _>(|attr| attr.path.is_ident("ruma_api"));
field.attrs = attrs;
for attr in mem::take(&mut field.attrs) {
let meta = match Meta::from_attribute(&attr)? {
Some(m) => m,
None => {
field.attrs.push(attr);
continue;
let kind_attr = match api_attrs.as_slice() {
[] => None,
[_] => Some(api_attrs.pop().unwrap().parse_args::<ResponseMeta>()?),
_ => {
return Err(syn::Error::new_spanned(
&api_attrs[1],
"multiple field kind attribute found, there can only be one",
));
}
};
if field_kind.is_some() {
return Err(syn::Error::new_spanned(
attr,
"There can only be one field kind attribute",
));
}
field_kind = Some(match meta {
Meta::Word(ident) => match &ident.to_string()[..] {
"body" => ResponseFieldKind::NewtypeBody,
"raw_body" => ResponseFieldKind::RawBody,
_ => {
return Err(syn::Error::new_spanned(
ident,
"Invalid #[ruma_api] argument with value, expected `body`",
));
}
},
Meta::NameValue(MetaNameValue { name, value }) => {
if name != "header" {
return Err(syn::Error::new_spanned(
name,
"Invalid #[ruma_api] argument with value, expected `header`",
));
}
header = Some(value);
ResponseFieldKind::Header
}
});
}
Ok(match field_kind.unwrap_or(ResponseFieldKind::Body) {
ResponseFieldKind::Body => ResponseField::Body(field),
ResponseFieldKind::Header => {
ResponseField::Header(field, header.expect("missing header name"))
}
ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field),
ResponseFieldKind::RawBody => ResponseField::RawBody(field),
})
Ok(ResponseField::new(field, kind_attr))
}
}
@ -293,14 +263,6 @@ impl ToTokens for ResponseField {
}
}
/// The types of fields that a response can have, without their values.
enum ResponseFieldKind {
Body,
Header,
NewtypeBody,
RawBody,
}
fn has_lifetime(ty: &Type) -> bool {
struct Visitor {
found_lifetime: bool,