use std::{convert::TryFrom, mem};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Field, Ident};
use crate::api::{
attribute::{Meta, MetaNameValue},
strip_serde_attrs, RawResponse,
};
pub struct Response {
fields: Vec<ResponseField>,
}
impl Response {
pub fn has_body_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_body())
}
pub fn has_header_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_header())
}
pub fn uses_wrap_incoming(&self) -> bool {
self.fields.iter().any(|f| f.has_wrap_incoming_attr())
}
pub fn init_fields(&self) -> TokenStream {
let fields = self.fields.iter().map(|response_field| match response_field {
ResponseField::Body(field) => {
let field_name =
field.ident.as_ref().expect("expected field to have an identifier");
let span = field.span();
quote_spanned! {span=>
#field_name: response_body.#field_name
}
}
ResponseField::Header(field, header_name) => {
let field_name =
field.ident.as_ref().expect("expected field to have an identifier");
let span = field.span();
quote_spanned! {span=>
#field_name: headers.remove(ruma_api::exports::http::header::#header_name)
.expect("response missing expected header")
.to_str()
.expect("failed to convert HeaderValue to str")
.to_owned()
}
}
ResponseField::NewtypeBody(field) => {
let field_name =
field.ident.as_ref().expect("expected field to have an identifier");
let span = field.span();
quote_spanned! {span=>
#field_name: response_body.0
}
}
});
quote! {
#(#fields,)*
}
}
pub fn apply_header_fields(&self) -> TokenStream {
let header_calls = self.fields.iter().filter_map(|response_field| {
if let ResponseField::Header(ref field, ref header_name) = *response_field {
let field_name =
field.ident.as_ref().expect("expected field to have an identifier");
let span = field.span();
Some(quote_spanned! {span=>
.header(ruma_api::exports::http::header::#header_name, response.#field_name)
})
} else {
None
}
});
quote! { #(#header_calls)* }
}
pub fn to_body(&self) -> TokenStream {
if let Some(field) = self.newtype_body_field() {
let field_name = field.ident.as_ref().expect("expected field to have an identifier");
let span = field.span();
quote_spanned!(span=> response.#field_name)
} else {
let fields = self.fields.iter().filter_map(|response_field| {
if let ResponseField::Body(ref field) = *response_field {
let field_name =
field.ident.as_ref().expect("expected field to have an identifier");
let span = field.span();
Some(quote_spanned! {span=>
#field_name: response.#field_name
})
} else {
None
}
});
quote! {
ResponseBody { #(#fields),* }
}
}
}
pub fn newtype_body_field(&self) -> Option<&Field> {
self.fields.iter().find_map(ResponseField::as_newtype_body_field)
}
}
impl TryFrom<RawResponse> for Response {
type Error = syn::Error;
fn try_from(raw: RawResponse) -> syn::Result<Self> {
let mut newtype_body_field = None;
let fields = raw
.fields
.into_iter()
.map(|mut field| {
let mut field_kind = None;
let mut header = None;
for attr in mem::replace(&mut field.attrs, Vec::new()) {
let meta = match Meta::from_attribute(&attr)? {
Some(m) => m,
None => {
field.attrs.push(attr);
continue;
}
};
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) => {
if ident != "body" {
return Err(syn::Error::new_spanned(
ident,
"Invalid #[ruma_api] argument with value, expected `body`",
));
}
if let Some(f) = &newtype_body_field {
let mut error = syn::Error::new_spanned(
field,
"There can only be one newtype body field",
);
error.combine(syn::Error::new_spanned(
f,
"Previous newtype body field",
));
return Err(error);
}
newtype_body_field = Some(field.clone());
ResponseFieldKind::NewtypeBody
}
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),
})
})
.collect::<syn::Result<Vec<_>>>()?;
if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
return Err(syn::Error::new_spanned(
raw.response_kw,
"Can't have both a newtype body field and regular body fields",
));
}
Ok(Self { fields })
}
}
impl ToTokens for Response {
fn to_tokens(&self, tokens: &mut TokenStream) {
let response_def = if self.fields.is_empty() {
quote!(;)
} else {
let fields =
self.fields.iter().map(|response_field| strip_serde_attrs(response_field.field()));
quote! { { #(#fields),* } }
};
let (derive_deserialize, response_body_def) =
if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) {
let field = Field { ident: None, colon_token: None, ..body_field.field().clone() };
let derive_deserialize = if body_field.has_wrap_incoming_attr() {
TokenStream::new()
} else {
quote!(ruma_api::exports::serde::Deserialize)
};
(derive_deserialize, quote! { (#field); })
} else if self.has_body_fields() {
let fields = self.fields.iter().filter(|f| f.is_body());
let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) {
TokenStream::new()
} else {
quote!(ruma_api::exports::serde::Deserialize)
};
let fields = fields.map(ResponseField::field);
(derive_deserialize, quote!({ #(#fields),* }))
} else {
(quote!(ruma_api::exports::serde::Deserialize), quote!(;))
};
let response = quote! {
#[derive(Debug, Clone, ruma_api::Outgoing)]
#[incoming_no_deserialize]
pub struct Response #response_def
#[derive(
Debug,
ruma_api::Outgoing,
ruma_api::exports::serde::Serialize,
#derive_deserialize
)]
struct ResponseBody #response_body_def
};
response.to_tokens(tokens);
}
}
pub enum ResponseField {
Body(Field),
Header(Field, Ident),
NewtypeBody(Field),
}
impl ResponseField {
fn field(&self) -> &Field {
match self {
ResponseField::Body(field)
| ResponseField::Header(field, _)
| ResponseField::NewtypeBody(field) => field,
}
}
fn is_body(&self) -> bool {
self.as_body_field().is_some()
}
fn is_header(&self) -> bool {
match self {
ResponseField::Header(..) => true,
_ => false,
}
}
fn is_newtype_body(&self) -> bool {
self.as_newtype_body_field().is_some()
}
fn as_body_field(&self) -> Option<&Field> {
match self {
ResponseField::Body(field) => Some(field),
_ => None,
}
}
fn as_newtype_body_field(&self) -> Option<&Field> {
match self {
ResponseField::NewtypeBody(field) => Some(field),
_ => None,
}
}
fn has_wrap_incoming_attr(&self) -> bool {
self.field().attrs.iter().any(|attr| {
attr.path.segments.len() == 1 && attr.path.segments[0].ident == "wrap_incoming"
})
}
}
enum ResponseFieldKind {
Body,
Header,
NewtypeBody,
}