diff --git a/crates/ruma-macros/src/api/attribute.rs b/crates/ruma-macros/src/api/attribute.rs index dc772baa..d0a1d50b 100644 --- a/crates/ruma-macros/src/api/attribute.rs +++ b/crates/ruma-macros/src/api/attribute.rs @@ -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 -#[allow(clippy::large_enum_variant)] -pub enum MetaValue { - Lit(Lit), - Type(Type), +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); } -impl Parse for MetaValue { +pub enum RequestMeta { + NewtypeBody, + RawBody, + Path, + Query, + QueryMap, + Header(Ident), +} + +impl Parse for RequestMeta { fn parse(input: ParseStream<'_>) -> syn::Result { - 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 { - /// The part left of the equals sign - pub name: Ident, - - /// The part right of the equals sign - pub value: V, -} - -impl Parse for MetaNameValue { - fn parse(input: ParseStream<'_>) -> syn::Result { - let ident = 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 { - /// 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), -} - -impl Meta { - /// Check if the given attribute is a ruma_api attribute. - /// - /// If it is, parse it. - pub fn from_attribute(attr: &syn::Attribute) -> syn::Result> { - if attr.path.is_ident("ruma_api") { - attr.parse_args().map(Some) - } else { - Ok(None) - } - } -} - -impl Parse for Meta { - fn parse(input: ParseStream<'_>) -> syn::Result { - let ident = input.parse()?; - - if input.peek(Token![=]) { + 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()?; - Ok(Meta::NameValue(MetaNameValue { name: ident, value: input.parse()? })) + input.parse().map(Self::Header) } else { - Ok(Meta::Word(ident)) + 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 { + 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 { + 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 DeriveResponseMeta { + ManualBodySerde, + ErrorTy(Type), +} + +impl Parse for DeriveResponseMeta { + fn parse(input: ParseStream<'_>) -> syn::Result { + 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()?; + input.parse().map(Self::ErrorTy) + } else { + Err(lookahead.error()) } } } diff --git a/crates/ruma-macros/src/api/request.rs b/crates/ruma-macros/src/api/request.rs index bf2031b3..77a0c96b 100644 --- a/crates/ruma-macros/src/api/request.rs +++ b/crates/ruma-macros/src/api/request.rs @@ -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 { 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::::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) -> 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) -> 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 for RequestField { type Error = syn::Error; fn try_from(mut field: Field) -> syn::Result { - let mut field_kind = None; - let mut header = None; + let (mut api_attrs, attrs) = + field.attrs.into_iter().partition::, _>(|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; - } - }; - - if field_kind.is_some() { + let kind_attr = match api_attrs.as_slice() { + [] => None, + [_] => Some(api_attrs.pop().unwrap().parse_args::()?), + _ => { return Err(syn::Error::new_spanned( - attr, - "There can only be one field kind attribute", + &api_attrs[1], + "multiple field kind attribute found, there can only be one", )); } + }; - 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, -} diff --git a/crates/ruma-macros/src/api/response.rs b/crates/ruma-macros/src/api/response.rs index b7c7c84a..0017524b 100644 --- a/crates/ruma-macros/src/api/response.rs +++ b/crates/ruma-macros/src/api/response.rs @@ -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 { continue; } - let metas = attr.parse_args_with(Punctuated::, Token![,]>::parse_terminated)?; + let metas = + attr.parse_args_with(Punctuated::::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) -> 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 for ResponseField { )); } - let mut field_kind = None; - let mut header = None; + let (mut api_attrs, attrs) = + field.attrs.into_iter().partition::, _>(|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; - } - }; - - if field_kind.is_some() { + let kind_attr = match api_attrs.as_slice() { + [] => None, + [_] => Some(api_attrs.pop().unwrap().parse_args::()?), + _ => { return Err(syn::Error::new_spanned( - attr, - "There can only be one field kind attribute", + &api_attrs[1], + "multiple field kind attribute found, there can only be one", )); } + }; - 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,