use cfg_if::cfg_if;
use proc_macro2::TokenStream;
use quote::quote;
use super::{Body, Headers, MacroKind, StructSuffix, ensure_feature_presence};
use crate::util::{
PrivateField, RumaCommon, RumaCommonReexport, StructFieldExt, expand_fields_as_list,
};
mod incoming;
mod outgoing;
mod parse;
pub(crate) use self::parse::RequestAttrs;
const KIND: MacroKind = MacroKind::Request;
pub fn expand_request(attrs: RequestAttrs, item: syn::ItemStruct) -> TokenStream {
let ruma_common = RumaCommon::new();
let ruma_macros = ruma_common.reexported(RumaCommonReexport::RumaMacros);
let maybe_feature_error = ensure_feature_presence().map(syn::Error::to_compile_error);
let error_ty = attrs.error_ty_or_default(&ruma_common);
cfg_if! {
if #[cfg(feature = "__internal_macro_expand")] {
use syn::parse_quote;
let mut derive_input = item.clone();
derive_input.attrs.push(parse_quote! { #[ruma_api(error = #error_ty)] });
crate::util::cfg_expand_struct(&mut derive_input);
let extra_derive = quote! { #ruma_macros::_FakeDeriveRumaApi };
let ruma_api_attribute = quote! {};
let request_impls =
expand_derive_request(derive_input).unwrap_or_else(syn::Error::into_compile_error);
} else {
let extra_derive = quote! { #ruma_macros::Request };
let ruma_api_attribute = quote! { #[ruma_api(error = #error_ty)] };
let request_impls = quote! {};
}
}
quote! {
#maybe_feature_error
#[derive(Clone, Debug, #ruma_common::serde::_FakeDeriveSerde, #extra_derive)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#ruma_api_attribute
#item
#request_impls
}
}
pub fn expand_derive_request(input: syn::ItemStruct) -> syn::Result<TokenStream> {
let request = Request::try_from(input)?;
let ruma_common = RumaCommon::new();
let impls = request.expand_impls(&ruma_common);
let tests = request.expand_tests(&ruma_common);
Ok(quote! {
#impls
#[allow(deprecated)]
#[cfg(test)]
mod __request {
#tests
}
})
}
struct Request {
ident: syn::Ident,
generics: syn::Generics,
headers: Headers,
path: RequestPath,
query: RequestQuery,
body: Body,
error_ty: syn::Type,
}
impl Request {
fn expand_impls(&self, ruma_common: &RumaCommon) -> TokenStream {
let ruma_macros = ruma_common.reexported(RumaCommonReexport::RumaMacros);
let serde = ruma_common.reexported(RumaCommonReexport::Serde);
let request_body_serde_struct = self.body.expand_serde_struct_definition(KIND, ruma_common);
let request_query_serde_struct =
self.query.expand_serde_struct_definition(&ruma_macros, &serde);
let outgoing_request_impl = self.expand_outgoing(ruma_common);
let incoming_request_impl = self.expand_incoming(ruma_common);
quote! {
#request_body_serde_struct
#request_query_serde_struct
#[allow(deprecated)]
mod __request_impls {
use super::*;
#outgoing_request_impl
#incoming_request_impl
}
}
}
fn expand_tests(&self, ruma_common: &RumaCommon) -> TokenStream {
let ident = &self.ident;
let mut tests = self.path.expand_tests(ident, ruma_common);
if !self.body.is_empty() {
let http = ruma_common.reexported(RumaCommonReexport::Http);
tests.extend(quote! {
#[::std::prelude::v1::test]
fn request_is_not_get() {
::std::assert_ne!(
<super::#ident as #ruma_common::api::Metadata>::METHOD, #http::Method::GET,
"GET endpoints can't have body fields",
);
}
});
}
tests
}
}
#[derive(Default)]
pub struct RequestPath(Vec<syn::Field>);
impl RequestPath {
fn expand_tests(&self, ident: &syn::Ident, ruma_common: &RumaCommon) -> TokenStream {
let path_fields = self.0.iter().map(|f| f.ident().to_string());
quote! {
#[::std::prelude::v1::test]
fn path_parameters() {
use #ruma_common::api::path_builder::PathBuilder as _;
let path_params = <super::#ident as #ruma_common::api::Metadata>::PATH_BUILDER._path_parameters();
let request_path_fields: &[&::std::primitive::str] = &[#(#path_fields),*];
::std::assert_eq!(
path_params, request_path_fields,
"Path parameters must match the `Request`'s `#[ruma_api(path)]` fields"
);
}
}
}
fn expand_fields(&self) -> TokenStream {
expand_fields_as_list(&self.0)
}
}
#[derive(Default)]
#[allow(clippy::large_enum_variant)]
enum RequestQuery {
#[default]
None,
Fields(Vec<syn::Field>),
All(syn::Field),
}
impl RequestQuery {
fn expand_serde_struct_definition(
&self,
ruma_macros: &TokenStream,
serde: &TokenStream,
) -> Option<TokenStream> {
let (fields, extra_attrs) = match self {
Self::None => return None,
Self::Fields(fields) => (fields.as_slice(), None),
Self::All(field) => {
let extra_attrs = quote! { #[serde(transparent)] };
(std::slice::from_ref(field), Some(extra_attrs))
}
};
let fields = fields.iter().map(PrivateField);
let ident = KIND.as_struct_ident(StructSuffix::Query);
Some(quote! {
#[cfg(any(feature = "client", feature = "server"))]
#[derive(Debug, #ruma_macros::_FakeDeriveRumaApi, #ruma_macros::_FakeDeriveSerde)]
#[cfg_attr(feature = "client", derive(#serde::Serialize))]
#[cfg_attr(feature = "server", derive(#serde::Deserialize))]
#extra_attrs
struct #ident { #( #fields ),* }
})
}
fn expand_fields(&self) -> Option<TokenStream> {
let fields = match self {
Self::None => return None,
Self::Fields(fields) => fields.as_slice(),
Self::All(field) => std::slice::from_ref(field),
};
Some(expand_fields_as_list(fields))
}
}