use proc_macro2::Span;
use syn::{meta::ParseNestedMeta, parse_quote};
use super::{Request, RequestPath, RequestQuery};
use crate::{
api::{Body, Headers},
util::{ParseNestedMetaExt, RumaCommon},
};
impl Request {
fn validate(&self) -> syn::Result<()> {
self.path.validate()?;
self.body.validate()?;
Ok(())
}
}
impl TryFrom<syn::ItemStruct> for Request {
type Error = syn::Error;
fn try_from(input: syn::ItemStruct) -> Result<Self, Self::Error> {
let mut request_attrs = RequestAttrs::default();
for attr in input.attrs {
if !attr.path().is_ident("ruma_api") {
continue;
}
attr.parse_nested_meta(|meta| request_attrs.try_merge(meta))?;
}
let mut request = Request {
ident: input.ident,
generics: input.generics,
headers: Headers::default(),
path: RequestPath::default(),
query: RequestQuery::default(),
body: Body::default(),
error_ty: request_attrs
.error_ty
.ok_or_else(|| syn::Error::new(Span::call_site(), "missing `error` attribute"))?,
};
for field in input.fields {
let RequestField { inner: field, kind } = field.try_into()?;
match kind {
RequestFieldKind::Body => request.body.push_json_field(field)?,
RequestFieldKind::NewtypeBody => request.body.set_json_all(field)?,
RequestFieldKind::RawBody => request.body.set_raw(field)?,
RequestFieldKind::Path => {
request.path.push(field);
}
RequestFieldKind::Query => request.query.push_field(field)?,
RequestFieldKind::QueryAll => request.query.set_all(field)?,
RequestFieldKind::Header { name } => {
request.headers.insert(name, field)?;
}
}
}
request.validate()?;
Ok(request)
}
}
impl RequestPath {
fn push(&mut self, field: syn::Field) {
self.0.push(field);
}
fn validate(&self) -> syn::Result<()> {
for field in &self.0 {
if field.attrs.iter().any(|attr| attr.path().is_ident("cfg")) {
return Err(syn::Error::new_spanned(
field,
"`#[cfg]` attribute is not supported on `path` fields",
));
}
}
Ok(())
}
}
impl RequestQuery {
fn push_field(&mut self, field: syn::Field) -> syn::Result<()> {
match self {
Self::None => {
*self = Self::Fields(vec![field]);
Ok(())
}
Self::Fields(fields) => {
fields.push(field);
Ok(())
}
Self::All(_) => Err(syn::Error::new(
Span::call_site(),
"cannot have both a `query_all` field and `query` fields",
)),
}
}
fn set_all(&mut self, field: syn::Field) -> syn::Result<()> {
let error_msg = match self {
Self::None => {
*self = Self::All(field);
return Ok(());
}
Self::Fields(_) => "cannot have both a `query_all` field and `query` fields",
Self::All(_) => "cannot have multiple `query_all` fields",
};
Err(syn::Error::new(Span::call_site(), error_msg))
}
}
#[derive(Default)]
pub(crate) struct RequestAttrs {
error_ty: Option<syn::Type>,
}
impl RequestAttrs {
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` request attribute",
));
}
self.error_ty = Some(error_ty);
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()?);
}
Err(meta.error("unsupported `request` 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 })
}
}
struct RequestField {
inner: syn::Field,
kind: RequestFieldKind,
}
impl RequestField {
fn set_kind(&mut self, kind: RequestFieldKind) -> syn::Result<()> {
if !matches!(self.kind, RequestFieldKind::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) = RequestFieldKind::try_from_meta(&meta)? {
return self.set_kind(kind);
}
Err(meta.error("unsupported `ruma_api` field attribute"))
}
}
impl TryFrom<syn::Field> for RequestField {
type Error = syn::Error;
fn try_from(inner: syn::Field) -> syn::Result<Self> {
let mut field = RequestField { 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 RequestFieldKind {
#[default]
Body,
NewtypeBody,
RawBody,
Path,
Query,
QueryAll,
Header {
name: syn::Ident,
},
}
impl RequestFieldKind {
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))
}
"path" => {
if meta.has_value() {
return Err(meta.error("`path` attribute doesn't expect a value"));
}
Ok(Some(Self::Path))
}
"query" => {
if meta.has_value() {
return Err(meta.error("`query` attribute doesn't expect a value"));
}
Ok(Some(Self::Query))
}
"query_all" => {
if meta.has_value() {
return Err(meta.error("`query_all` attribute doesn't expect a value"));
}
Ok(Some(Self::QueryAll))
}
"header" => {
let name = meta.value()?.parse()?;
Ok(Some(Self::Header { name }))
}
_ => Ok(None),
}
}
}