macros: Refactor ResponseField

This commit is contained in:
Jonas Platte 2022-09-26 16:03:05 +02:00
parent a59a648d04
commit 2dbaf19ded
No known key found for this signature in database
GPG Key ID: AAA7A61F696C3E0C
2 changed files with 42 additions and 47 deletions

View File

@ -64,22 +64,22 @@ impl Response {
fn has_body_fields(&self) -> bool { fn has_body_fields(&self) -> bool {
self.fields self.fields
.iter() .iter()
.any(|f| matches!(f, ResponseField::Body(_) | &ResponseField::NewtypeBody(_))) .any(|f| matches!(&f.kind, ResponseFieldKind::Body | &ResponseFieldKind::NewtypeBody))
} }
/// Whether or not this request has a single newtype body field. /// Whether or not this request has a single newtype body field.
fn has_newtype_body(&self) -> bool { fn has_newtype_body(&self) -> bool {
self.fields.iter().any(|f| matches!(f, ResponseField::NewtypeBody(_))) self.fields.iter().any(|f| matches!(&f.kind, ResponseFieldKind::NewtypeBody))
} }
/// Whether or not this request has a single raw body field. /// Whether or not this request has a single raw body field.
fn has_raw_body(&self) -> bool { fn has_raw_body(&self) -> bool {
self.fields.iter().any(|f| matches!(f, ResponseField::RawBody(_))) self.fields.iter().any(|f| matches!(&f.kind, ResponseFieldKind::RawBody))
} }
/// Whether or not this request has any data in the URL path. /// Whether or not this request has any data in the URL path.
fn has_header_fields(&self) -> bool { fn has_header_fields(&self) -> bool {
self.fields.iter().any(|f| matches!(f, &ResponseField::Header(..))) self.fields.iter().any(|f| matches!(&f.kind, &ResponseFieldKind::Header(_)))
} }
fn expand_all(&self) -> TokenStream { fn expand_all(&self) -> TokenStream {
@ -127,10 +127,9 @@ impl Response {
"This macro doesn't support generic types" "This macro doesn't support generic types"
); );
let newtype_body_fields = self let newtype_body_fields = self.fields.iter().filter(|f| {
.fields matches!(&f.kind, ResponseFieldKind::NewtypeBody | ResponseFieldKind::RawBody)
.iter() });
.filter(|f| matches!(f, ResponseField::NewtypeBody(_) | ResponseField::RawBody(_)));
let has_newtype_body_field = match newtype_body_fields.count() { let has_newtype_body_field = match newtype_body_fields.count() {
0 => false, 0 => false,
@ -143,7 +142,8 @@ impl Response {
} }
}; };
let has_body_fields = self.fields.iter().any(|f| matches!(f, ResponseField::Body(_))); let has_body_fields =
self.fields.iter().any(|f| matches!(&f.kind, ResponseFieldKind::Body));
if has_newtype_body_field && has_body_fields { if has_newtype_body_field && has_body_fields {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
&self.ident, &self.ident,
@ -155,65 +155,60 @@ impl Response {
} }
} }
/// The types of fields that a response can have. /// A field of the response struct.
enum ResponseField { struct ResponseField {
inner: Field,
kind: ResponseFieldKind,
}
/// The kind of a response field.
enum ResponseFieldKind {
/// JSON data in the body of the response. /// JSON data in the body of the response.
Body(Field), Body,
/// Data in an HTTP header. /// Data in an HTTP header.
Header(Field, Ident), Header(Ident),
/// A specific data type in the body of the response. /// A specific data type in the body of the response.
NewtypeBody(Field), NewtypeBody,
/// Arbitrary bytes in the body of the response. /// Arbitrary bytes in the body of the response.
RawBody(Field), RawBody,
} }
impl ResponseField { impl ResponseField {
/// Creates a new `ResponseField`. /// Creates a new `ResponseField`.
fn new(field: Field, kind_attr: Option<ResponseMeta>) -> Self { fn new(inner: Field, kind_attr: Option<ResponseMeta>) -> Self {
if let Some(attr) = kind_attr { let kind = match kind_attr {
match attr { Some(ResponseMeta::NewtypeBody) => ResponseFieldKind::NewtypeBody,
ResponseMeta::NewtypeBody => ResponseField::NewtypeBody(field), Some(ResponseMeta::RawBody) => ResponseFieldKind::RawBody,
ResponseMeta::RawBody => ResponseField::RawBody(field), Some(ResponseMeta::Header(header)) => ResponseFieldKind::Header(header),
ResponseMeta::Header(header) => ResponseField::Header(field, header), None => ResponseFieldKind::Body,
} };
} else {
ResponseField::Body(field)
}
}
/// Gets the inner `Field` value. Self { inner, kind }
fn field(&self) -> &Field {
match self {
ResponseField::Body(field)
| ResponseField::Header(field, _)
| ResponseField::NewtypeBody(field)
| ResponseField::RawBody(field) => field,
}
} }
/// Return the contained field if this response field is a body kind. /// Return the contained field if this response field is a body kind.
fn as_body_field(&self) -> Option<&Field> { fn as_body_field(&self) -> Option<&Field> {
match self { match &self.kind {
ResponseField::Body(field) | ResponseField::NewtypeBody(field) => Some(field), ResponseFieldKind::Body | ResponseFieldKind::NewtypeBody => Some(&self.inner),
_ => None, _ => None,
} }
} }
/// Return the contained field if this response field is a raw body kind. /// Return the contained field if this response field is a raw body kind.
fn as_raw_body_field(&self) -> Option<&Field> { fn as_raw_body_field(&self) -> Option<&Field> {
match self { match &self.kind {
ResponseField::RawBody(field) => Some(field), ResponseFieldKind::RawBody => Some(&self.inner),
_ => None, _ => None,
} }
} }
/// Return the contained field and HTTP header ident if this response field is a header kind. /// Return the contained field and HTTP header ident if this response field is a header kind.
fn as_header_field(&self) -> Option<(&Field, &Ident)> { fn as_header_field(&self) -> Option<(&Field, &Ident)> {
match self { match &self.kind {
ResponseField::Header(field, ident) => Some((field, ident)), ResponseFieldKind::Header(ident) => Some((&self.inner, ident)),
_ => None, _ => None,
} }
} }
@ -257,7 +252,7 @@ impl Parse for ResponseField {
impl ToTokens for ResponseField { impl ToTokens for ResponseField {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
self.field().to_tokens(tokens); self.inner.to_tokens(tokens);
} }
} }

View File

@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::Type; use syn::Type;
use super::{Response, ResponseField}; use super::{Response, ResponseFieldKind};
impl Response { impl Response {
pub fn expand_incoming(&self, error_ty: &Type, ruma_common: &TokenStream) -> TokenStream { pub fn expand_incoming(&self, error_ty: &Type, ruma_common: &TokenStream) -> TokenStream {
@ -38,20 +38,20 @@ impl Response {
let mut raw_body = None; let mut raw_body = None;
for response_field in &self.fields { for response_field in &self.fields {
let field = response_field.field(); let field = &response_field.inner;
let field_name = let field_name =
field.ident.as_ref().expect("expected field to have an identifier"); field.ident.as_ref().expect("expected field to have an identifier");
let cfg_attrs = let cfg_attrs =
field.attrs.iter().filter(|a| a.path.is_ident("cfg")).collect::<Vec<_>>(); field.attrs.iter().filter(|a| a.path.is_ident("cfg")).collect::<Vec<_>>();
fields.push(match response_field { fields.push(match &response_field.kind {
ResponseField::Body(_) | ResponseField::NewtypeBody(_) => { ResponseFieldKind::Body | ResponseFieldKind::NewtypeBody => {
quote! { quote! {
#( #cfg_attrs )* #( #cfg_attrs )*
#field_name: response_body.#field_name #field_name: response_body.#field_name
} }
} }
ResponseField::Header(_, header_name) => { ResponseFieldKind::Header(header_name) => {
let optional_header = match &field.ty { let optional_header = match &field.ty {
syn::Type::Path(syn::TypePath { syn::Type::Path(syn::TypePath {
path: syn::Path { segments, .. }, path: syn::Path { segments, .. },
@ -81,7 +81,7 @@ impl Response {
// This field must be instantiated last to avoid `use of move value` error. // This field must be instantiated last to avoid `use of move value` error.
// We are guaranteed only one new body field because of a check in // We are guaranteed only one new body field because of a check in
// `parse_response`. // `parse_response`.
ResponseField::RawBody(_) => { ResponseFieldKind::RawBody => {
raw_body = Some(quote! { raw_body = Some(quote! {
#( #cfg_attrs )* #( #cfg_attrs )*
#field_name: { #field_name: {