use proc_macro2::Span;
use syn::{meta::ParseNestedMeta, parse_quote};
use super::Response;
use crate::{
api::{Body, Headers},
util::{ParseNestedMetaExt, RumaCommon, TypeExt},
};
impl Response {
fn validate(&self) -> syn::Result<()> {
if !self.generics.params.is_empty() || self.generics.where_clause.is_some() {
return Err(syn::Error::new(
Span::call_site(),
"the `response` macro doesn't support generic types",
));
}
self.body.validate()?;
Ok(())
}
}
impl TryFrom<syn::ItemStruct> for Response {
type Error = syn::Error;
fn try_from(input: syn::ItemStruct) -> Result<Self, Self::Error> {
let mut response_attrs = ResponseAttrs::default();
for attr in input.attrs {
if !attr.path().is_ident("ruma_api") {
continue;
}
attr.parse_nested_meta(|meta| response_attrs.try_merge(meta))?;
}
let mut response = Response {
ident: input.ident,
generics: input.generics,
headers: Headers::default(),
body: Body::default(),
error_ty: response_attrs
.error_ty
.ok_or_else(|| syn::Error::new(Span::call_site(), "missing `error` attribute"))?,
status: response_attrs
.status
.ok_or_else(|| syn::Error::new(Span::call_site(), "missing `status` attribute"))?,
};
response.body.set_manual_serde(response_attrs.manual_body_serde);
for field in input.fields {
let ResponseField { inner: field, kind } = field.try_into()?;
match kind {
ResponseFieldKind::Body => response.body.push_json_field(field)?,
ResponseFieldKind::NewtypeBody => response.body.set_json_all(field)?,
ResponseFieldKind::RawBody => response.body.set_raw(field)?,
ResponseFieldKind::Header { name } => {
response.headers.insert(name, field)?;
}
}
}
response.validate()?;
Ok(response)
}
}
#[derive(Default)]
pub(crate) struct ResponseAttrs {
error_ty: Option<syn::Type>,
status: Option<syn::Ident>,
pub(super) manual_body_serde: bool,
}
impl ResponseAttrs {
fn set_error_ty(&mut self, error_ty: syn::Type) -> syn::Result<()> {
if self.error_ty.is_some() {
return Err(syn::Error::new(
Span::call_site(),
"cannot have multiple values for `error` response attribute",
));
}
self.error_ty = Some(error_ty);
Ok(())
}
fn set_status(&mut self, status: syn::Ident) -> syn::Result<()> {
if self.status.is_some() {
return Err(syn::Error::new(
Span::call_site(),
"cannot have multiple values for `status` response attribute",
));
}
self.status = Some(status);
Ok(())
}
fn set_manual_body_serde(&mut self) -> syn::Result<()> {
if self.manual_body_serde {
return Err(syn::Error::new(
Span::call_site(),
"cannot have multiple `manual_body_serde` response attributes",
));
}
self.manual_body_serde = true;
Ok(())
}
pub(crate) fn try_merge(&mut self, meta: ParseNestedMeta<'_>) -> syn::Result<()> {
if meta.path.is_ident("error") {
return self.set_error_ty(meta.value()?.parse()?);
}
if meta.path.is_ident("status") {
return self.set_status(meta.value()?.parse()?);
}
if meta.path.is_ident("manual_body_serde") {
return self.set_manual_body_serde();
}
Err(meta.error("unsupported `response` attribute"))
}
pub(super) fn error_ty_or_default(&self, ruma_common: &RumaCommon) -> syn::Type {
self.error_ty.clone().unwrap_or_else(|| parse_quote! { #ruma_common::api::error::Error })
}
pub(super) fn status_or_default(&self) -> syn::Ident {
self.status.clone().unwrap_or_else(|| parse_quote! { OK })
}
}
struct ResponseField {
inner: syn::Field,
kind: ResponseFieldKind,
}
impl ResponseField {
fn set_kind(&mut self, kind: ResponseFieldKind) -> syn::Result<()> {
if !matches!(self.kind, ResponseFieldKind::Body) {
return Err(syn::Error::new_spanned(
&self.inner,
"multiple request field kind attributes found, there can only be one",
));
}
self.kind = kind;
Ok(())
}
fn try_merge(&mut self, meta: ParseNestedMeta<'_>) -> syn::Result<()> {
if let Some(kind) = ResponseFieldKind::try_from_meta(&meta)? {
return self.set_kind(kind);
}
Err(meta.error("unsupported `ruma_api` field attribute"))
}
}
impl TryFrom<syn::Field> for ResponseField {
type Error = syn::Error;
fn try_from(inner: syn::Field) -> syn::Result<Self> {
if inner.ty.has_lifetime() {
return Err(syn::Error::new_spanned(
inner,
"lifetimes on response fields cannot be supported until GAT are stable",
));
}
let mut field = ResponseField { inner, kind: Default::default() };
let api_attrs = field
.inner
.attrs
.extract_if(.., |attr| attr.path().is_ident("ruma_api"))
.collect::<Vec<_>>();
for attr in api_attrs {
attr.parse_nested_meta(|meta| field.try_merge(meta))?;
}
Ok(field)
}
}
#[derive(Default)]
enum ResponseFieldKind {
#[default]
Body,
NewtypeBody,
RawBody,
Header {
name: syn::Ident,
},
}
impl ResponseFieldKind {
fn try_from_meta(meta: &ParseNestedMeta<'_>) -> syn::Result<Option<Self>> {
let Some(ident) = meta.path.get_ident() else {
return Ok(None);
};
match ident.to_string().as_str() {
"body" => {
if meta.has_value() {
return Err(meta.error("`body` attribute doesn't expect a value"));
}
Ok(Some(Self::NewtypeBody))
}
"raw_body" => {
if meta.has_value() {
return Err(meta.error("`raw_body` attribute doesn't expect a value"));
}
Ok(Some(Self::RawBody))
}
"header" => {
let name = meta.value()?.parse()?;
Ok(Some(Self::Header { name }))
}
_ => Ok(None),
}
}
}