diff --git a/crates/ruma-common/src/api.rs b/crates/ruma-common/src/api.rs index 743d0d1e..6174d083 100644 --- a/crates/ruma-common/src/api.rs +++ b/crates/ruma-common/src/api.rs @@ -194,6 +194,44 @@ use crate::UserId; /// ``` pub use ruma_macros::ruma_api; +/// Generates [`OutgoingRequest`] and [`IncomingRequest`] implementations. +/// +/// The `OutgoingRequest` impl is be on the `Request` type this attribute is used on. It is +/// feature-gated behind `cfg(feature = "client")`. +/// +/// The `IncomingRequest` impl is be on `IncomingRequest`, which is either a type alias to +/// `Request` or a fully-owned version of the same, depending of whether `Request` has any +/// lifetime parameters. +/// +/// The generated code expects a `METADATA` constant of type [`Metadata`] to be in scope, +/// alongside a `Response` type that implements both [`OutgoingResponse`] and +/// [`IncomingResponse`]. +/// +/// ## Attributes +/// +/// To declare which part of the request a field belongs to: +/// +/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP +/// headers on the request. The value must implement `AsRef`. Generally this is a +/// `String`. The attribute value shown above as `HEADER_NAME` must be a `const` expression +/// of the type `http::header::HeaderName`, like one of the constants from `http::header`, +/// e.g. `CONTENT_TYPE`. +/// * `#[ruma_api(path)]`: Fields with this attribute will be inserted into the matching path +/// component of the request URL. +/// * `#[ruma_api(query)]`: Fields with this attribute will be inserting into the URL's query +/// string. +/// * `#[ruma_api(query_map)]`: Instead of individual query fields, one query_map field, of any +/// type that implements `IntoIterator` (e.g. `HashMap`, can be used for cases where an endpoint supports arbitrary query parameters. +/// * `#[ruma_api(body)]`: Use this if multiple endpoints should share a request body type, or +/// the request body is better expressed as an `enum` rather than a `struct`. The value of +/// the field will be used as the JSON body (rather than being a field in the request body +/// object). +/// * `#[ruma_api(raw_body)]`: Like `body` in that the field annotated with it represents the +/// entire request body, but this attribute is for endpoints where the body can be anything, +/// not just JSON. The field type must be `Vec`. +pub use ruma_macros::request; + pub mod error; mod metadata; diff --git a/crates/ruma-macros/src/api/api_request.rs b/crates/ruma-macros/src/api/api_request.rs index 91e1c40b..614c94d9 100644 --- a/crates/ruma-macros/src/api/api_request.rs +++ b/crates/ruma-macros/src/api/api_request.rs @@ -89,16 +89,7 @@ impl Request { quote! { #[doc = #docs] - #[derive( - Clone, - Debug, - #ruma_macros::Request, - #ruma_common::serde::Incoming, - #ruma_common::serde::_FakeDeriveSerde, - )] - #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] - #[incoming_derive(!Deserialize, #ruma_macros::_FakeDeriveRumaApi)] - #[ruma_api(error = #error_ty)] + #[#ruma_macros::request(error = #error_ty)] #( #struct_attributes )* pub struct #request_ident < #(#lifetimes),* > { #fields diff --git a/crates/ruma-macros/src/api/request.rs b/crates/ruma-macros/src/api/request.rs index 76c5447e..1b393b5f 100644 --- a/crates/ruma-macros/src/api/request.rs +++ b/crates/ruma-macros/src/api/request.rs @@ -5,7 +5,7 @@ use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, - DeriveInput, Field, Generics, Ident, Lifetime, Token, Type, + DeriveInput, Field, Generics, Ident, ItemStruct, Lifetime, Token, Type, }; use super::{ @@ -17,6 +17,38 @@ use crate::util::import_ruma_common; mod incoming; mod outgoing; +pub fn expand_request(attr: RequestAttr, item: ItemStruct) -> TokenStream { + let ruma_common = import_ruma_common(); + let ruma_macros = quote! { #ruma_common::exports::ruma_macros }; + + let error_ty = attr.0.first().map_or_else( + || quote! { #ruma_common::api::error::MatrixError }, + |DeriveRequestMeta::Error(ty)| quote! { #ty }, + ); + + quote! { + #[derive( + Clone, + Debug, + #ruma_macros::Request, + #ruma_common::serde::Incoming, + #ruma_common::serde::_FakeDeriveSerde, + )] + #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] + #[incoming_derive(!Deserialize, #ruma_macros::_FakeDeriveRumaApi)] + #[ruma_api(error = #error_ty)] + #item + } +} + +pub struct RequestAttr(Punctuated); + +impl Parse for RequestAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + Punctuated::::parse_terminated(input).map(Self) + } +} + pub fn expand_derive_request(input: DeriveInput) -> syn::Result { let fields = match input.data { syn::Data::Struct(s) => s.fields, diff --git a/crates/ruma-macros/src/lib.rs b/crates/ruma-macros/src/lib.rs index 1a6e8e1c..4f7c8cc7 100644 --- a/crates/ruma-macros/src/lib.rs +++ b/crates/ruma-macros/src/lib.rs @@ -25,7 +25,11 @@ mod serde; mod util; use self::{ - api::{request::expand_derive_request, response::expand_derive_response, Api}, + api::{ + request::{expand_derive_request, expand_request}, + response::expand_derive_response, + Api, + }, events::{ event::expand_event, event_content::expand_event_content, @@ -384,6 +388,15 @@ pub fn ruma_api(input: TokenStream) -> TokenStream { api.expand_all().into() } +/// > ⚠ If this is the only documentation you see, please navigate to the docs for +/// > `ruma_common::api::request`, where actual documentation can be found. +#[proc_macro_attribute] +pub fn request(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr = parse_macro_input!(attr); + let item = parse_macro_input!(item); + expand_request(attr, item).into() +} + /// Internal helper taking care of the request-specific parts of `ruma_api!`. #[proc_macro_derive(Request, attributes(ruma_api))] pub fn derive_request(input: TokenStream) -> TokenStream {