api: Add status parameter to request attribute macro
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
This commit is contained in:
parent
6e763ee5e7
commit
8881755235
@ -217,6 +217,10 @@ pub use ruma_macros::request;
|
|||||||
///
|
///
|
||||||
/// The generated code expects a `METADATA` constant of type [`Metadata`] to be in scope.
|
/// The generated code expects a `METADATA` constant of type [`Metadata`] to be in scope.
|
||||||
///
|
///
|
||||||
|
/// The status code of `OutgoingResponse` can be optionally overridden by adding the `status`
|
||||||
|
/// attribute to `response`. The attribute value must be a status code constant from
|
||||||
|
/// `http::StatusCode`, e.g. `IM_A_TEAPOT`.
|
||||||
|
///
|
||||||
/// ## Attributes
|
/// ## Attributes
|
||||||
///
|
///
|
||||||
/// To declare which part of the request a field belongs to:
|
/// To declare which part of the request a field belongs to:
|
||||||
@ -260,7 +264,7 @@ pub use ruma_macros::request;
|
|||||||
/// # #[request]
|
/// # #[request]
|
||||||
/// # pub struct Request { }
|
/// # pub struct Request { }
|
||||||
///
|
///
|
||||||
/// #[response]
|
/// #[response(status = IM_A_TEAPOT)]
|
||||||
/// pub struct Response {
|
/// pub struct Response {
|
||||||
/// #[serde(skip_serializing_if = "Option::is_none")]
|
/// #[serde(skip_serializing_if = "Option::is_none")]
|
||||||
/// pub foo: Option<String>,
|
/// pub foo: Option<String>,
|
||||||
|
33
crates/ruma-common/tests/api/default_status.rs
Normal file
33
crates/ruma-common/tests/api/default_status.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#![allow(clippy::exhaustive_structs)]
|
||||||
|
|
||||||
|
use http::StatusCode;
|
||||||
|
use ruma_common::{
|
||||||
|
api::{request, response, Metadata, OutgoingResponse as _},
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
const METADATA: Metadata = metadata! {
|
||||||
|
method: GET,
|
||||||
|
rate_limited: false,
|
||||||
|
authentication: None,
|
||||||
|
history: {
|
||||||
|
unstable => "/_matrix/my/endpoint",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Request type for the `default_status` endpoint.
|
||||||
|
#[request]
|
||||||
|
pub struct Request {}
|
||||||
|
|
||||||
|
/// Response type for the `default_status` endpoint.
|
||||||
|
#[response]
|
||||||
|
pub struct Response {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_default_status() {
|
||||||
|
let res = Response {};
|
||||||
|
let http_res = res.try_into_http_response::<Vec<u8>>().unwrap();
|
||||||
|
|
||||||
|
// Test that we correctly changed the status code.
|
||||||
|
assert_eq!(http_res.status(), StatusCode::OK);
|
||||||
|
}
|
@ -2,9 +2,11 @@
|
|||||||
#![allow(unreachable_pub)]
|
#![allow(unreachable_pub)]
|
||||||
|
|
||||||
mod conversions;
|
mod conversions;
|
||||||
|
mod default_status;
|
||||||
mod header_override;
|
mod header_override;
|
||||||
mod manual_endpoint_impl;
|
mod manual_endpoint_impl;
|
||||||
mod no_fields;
|
mod no_fields;
|
||||||
mod optional_headers;
|
mod optional_headers;
|
||||||
mod ruma_api;
|
mod ruma_api;
|
||||||
mod ruma_api_macros;
|
mod ruma_api_macros;
|
||||||
|
mod status_override;
|
||||||
|
50
crates/ruma-common/tests/api/status_override.rs
Normal file
50
crates/ruma-common/tests/api/status_override.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#![allow(clippy::exhaustive_structs)]
|
||||||
|
|
||||||
|
use http::{
|
||||||
|
header::{Entry, LOCATION},
|
||||||
|
StatusCode,
|
||||||
|
};
|
||||||
|
use ruma_common::{
|
||||||
|
api::{request, response, Metadata, OutgoingResponse as _},
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
const METADATA: Metadata = metadata! {
|
||||||
|
method: GET,
|
||||||
|
rate_limited: false,
|
||||||
|
authentication: None,
|
||||||
|
history: {
|
||||||
|
unstable => "/_matrix/my/endpoint",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Request type for the `status_override` endpoint.
|
||||||
|
#[request]
|
||||||
|
pub struct Request {}
|
||||||
|
|
||||||
|
/// Response type for the `status_override` endpoint.
|
||||||
|
#[response(status = FOUND)]
|
||||||
|
pub struct Response {
|
||||||
|
#[ruma_api(header = LOCATION)]
|
||||||
|
pub location: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn response_status_override() {
|
||||||
|
let res = Response { location: Some("/_matrix/another/endpoint".into()) };
|
||||||
|
let mut http_res = res.try_into_http_response::<Vec<u8>>().unwrap();
|
||||||
|
|
||||||
|
// Test that we correctly changed the status code.
|
||||||
|
assert_eq!(http_res.status(), StatusCode::FOUND);
|
||||||
|
|
||||||
|
// Test that we correctly replaced the location,
|
||||||
|
// not adding another location header.
|
||||||
|
assert_eq!(
|
||||||
|
match http_res.headers_mut().entry(LOCATION) {
|
||||||
|
Entry::Occupied(occ) => occ.iter().count(),
|
||||||
|
_ => 0,
|
||||||
|
},
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(http_res.headers().get("location").unwrap(), "/_matrix/another/endpoint");
|
||||||
|
}
|
@ -14,6 +14,7 @@ mod kw {
|
|||||||
syn::custom_keyword!(header);
|
syn::custom_keyword!(header);
|
||||||
syn::custom_keyword!(error);
|
syn::custom_keyword!(error);
|
||||||
syn::custom_keyword!(manual_body_serde);
|
syn::custom_keyword!(manual_body_serde);
|
||||||
|
syn::custom_keyword!(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum RequestMeta {
|
pub enum RequestMeta {
|
||||||
@ -99,6 +100,7 @@ impl Parse for ResponseMeta {
|
|||||||
pub enum DeriveResponseMeta {
|
pub enum DeriveResponseMeta {
|
||||||
ManualBodySerde,
|
ManualBodySerde,
|
||||||
Error(Type),
|
Error(Type),
|
||||||
|
Status(Ident),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for DeriveResponseMeta {
|
impl Parse for DeriveResponseMeta {
|
||||||
@ -111,6 +113,10 @@ impl Parse for DeriveResponseMeta {
|
|||||||
let _: kw::error = input.parse()?;
|
let _: kw::error = input.parse()?;
|
||||||
let _: Token![=] = input.parse()?;
|
let _: Token![=] = input.parse()?;
|
||||||
input.parse().map(Self::Error)
|
input.parse().map(Self::Error)
|
||||||
|
} else if lookahead.peek(kw::status) {
|
||||||
|
let _: kw::status = input.parse()?;
|
||||||
|
let _: Token![=] = input.parse()?;
|
||||||
|
input.parse().map(Self::Status)
|
||||||
} else {
|
} else {
|
||||||
Err(lookahead.error())
|
Err(lookahead.error())
|
||||||
}
|
}
|
||||||
|
@ -32,13 +32,21 @@ pub fn expand_response(attr: ResponseAttr, item: ItemStruct) -> TokenStream {
|
|||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| quote! { #ruma_common::api::error::MatrixError });
|
.unwrap_or_else(|| quote! { #ruma_common::api::error::MatrixError });
|
||||||
|
let status_ident = attr
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.find_map(|a| match a {
|
||||||
|
DeriveResponseMeta::Status(ident) => Some(quote! { #ident }),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| quote! { OK });
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#maybe_feature_error
|
#maybe_feature_error
|
||||||
|
|
||||||
#[derive(Clone, Debug, #ruma_macros::Response, #ruma_common::serde::_FakeDeriveSerde)]
|
#[derive(Clone, Debug, #ruma_macros::Response, #ruma_common::serde::_FakeDeriveSerde)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
#[ruma_api(error = #error_ty)]
|
#[ruma_api(error = #error_ty, status = #status_ident)]
|
||||||
#item
|
#item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +68,7 @@ pub fn expand_derive_response(input: DeriveInput) -> syn::Result<TokenStream> {
|
|||||||
let fields = fields.into_iter().map(ResponseField::try_from).collect::<syn::Result<_>>()?;
|
let fields = fields.into_iter().map(ResponseField::try_from).collect::<syn::Result<_>>()?;
|
||||||
let mut manual_body_serde = false;
|
let mut manual_body_serde = false;
|
||||||
let mut error_ty = None;
|
let mut error_ty = None;
|
||||||
|
let mut status_ident = None;
|
||||||
for attr in input.attrs {
|
for attr in input.attrs {
|
||||||
if !attr.path().is_ident("ruma_api") {
|
if !attr.path().is_ident("ruma_api") {
|
||||||
continue;
|
continue;
|
||||||
@ -71,6 +80,7 @@ pub fn expand_derive_response(input: DeriveInput) -> syn::Result<TokenStream> {
|
|||||||
match meta {
|
match meta {
|
||||||
DeriveResponseMeta::ManualBodySerde => manual_body_serde = true,
|
DeriveResponseMeta::ManualBodySerde => manual_body_serde = true,
|
||||||
DeriveResponseMeta::Error(t) => error_ty = Some(t),
|
DeriveResponseMeta::Error(t) => error_ty = Some(t),
|
||||||
|
DeriveResponseMeta::Status(t) => status_ident = Some(t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,6 +91,7 @@ pub fn expand_derive_response(input: DeriveInput) -> syn::Result<TokenStream> {
|
|||||||
fields,
|
fields,
|
||||||
manual_body_serde,
|
manual_body_serde,
|
||||||
error_ty: error_ty.unwrap(),
|
error_ty: error_ty.unwrap(),
|
||||||
|
status_ident: status_ident.unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
response.check()?;
|
response.check()?;
|
||||||
@ -93,6 +104,7 @@ struct Response {
|
|||||||
fields: Vec<ResponseField>,
|
fields: Vec<ResponseField>,
|
||||||
manual_body_serde: bool,
|
manual_body_serde: bool,
|
||||||
error_ty: Type,
|
error_ty: Type,
|
||||||
|
status_ident: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
@ -145,7 +157,7 @@ impl Response {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let outgoing_response_impl = self.expand_outgoing(&ruma_common);
|
let outgoing_response_impl = self.expand_outgoing(&self.status_ident, &ruma_common);
|
||||||
let incoming_response_impl = self.expand_incoming(&self.error_ty, &ruma_common);
|
let incoming_response_impl = self.expand_incoming(&self.error_ty, &ruma_common);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
use super::{Response, ResponseField};
|
use super::{Response, ResponseField};
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
pub fn expand_outgoing(&self, ruma_common: &TokenStream) -> TokenStream {
|
pub fn expand_outgoing(&self, status_ident: &Ident, ruma_common: &TokenStream) -> TokenStream {
|
||||||
let bytes = quote! { #ruma_common::exports::bytes };
|
let bytes = quote! { #ruma_common::exports::bytes };
|
||||||
let http = quote! { #ruma_common::exports::http };
|
let http = quote! { #ruma_common::exports::http };
|
||||||
|
|
||||||
@ -68,6 +69,7 @@ impl Response {
|
|||||||
self,
|
self,
|
||||||
) -> ::std::result::Result<#http::Response<T>, #ruma_common::api::error::IntoHttpError> {
|
) -> ::std::result::Result<#http::Response<T>, #ruma_common::api::error::IntoHttpError> {
|
||||||
let mut resp_builder = #http::Response::builder()
|
let mut resp_builder = #http::Response::builder()
|
||||||
|
.status(#http::StatusCode::#status_ident)
|
||||||
.header(#http::header::CONTENT_TYPE, "application/json");
|
.header(#http::header::CONTENT_TYPE, "application/json");
|
||||||
|
|
||||||
if let Some(mut headers) = resp_builder.headers_mut() {
|
if let Some(mut headers) = resp_builder.headers_mut() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user