Re-add the Outgoing trait and derive macro

The Outgoing trait now has no attributes except incoming_no_deserialize,
it looks for any references and lifetimes in a struct declaration and
removes them. The point of the Outgoing trait is to allow references to
be serialized and owned values to come out of deserialization. It has no
ability to wrap values in Raw (then EventResult).
This commit is contained in:
Devin Ragotzy 2020-07-29 14:17:07 -04:00
parent 8650ae00da
commit c86f0b106b
7 changed files with 394 additions and 87 deletions

View File

@ -157,10 +157,11 @@ impl ToTokens for Api {
TokenStream::new() TokenStream::new()
}; };
let extract_request_body = let extract_request_body = if self.request.has_body_fields()
if self.request.has_body_fields() || self.request.newtype_body_field().is_some() { || self.request.newtype_body_field().is_some()
{
quote! { quote! {
let request_body: RequestBody = ::ruma_api::try_deserialize!( let request_body: <RequestBody as ::ruma_api::Outgoing>::Incoming = ::ruma_api::try_deserialize!(
request, request,
::ruma_api::exports::serde_json::from_slice(request.body().as_slice()) ::ruma_api::exports::serde_json::from_slice(request.body().as_slice())
); );
@ -187,10 +188,11 @@ impl ToTokens for Api {
TokenStream::new() TokenStream::new()
}; };
let typed_response_body_decl = let typed_response_body_decl = if self.response.has_body_fields()
if self.response.has_body_fields() || self.response.newtype_body_field().is_some() { || self.response.newtype_body_field().is_some()
{
quote! { quote! {
let response_body: ResponseBody = ::ruma_api::try_deserialize!( let response_body: <ResponseBody as ::ruma_api::Outgoing>::Incoming = ::ruma_api::try_deserialize!(
response, response,
::ruma_api::exports::serde_json::from_slice(response.body().as_slice()), ::ruma_api::exports::serde_json::from_slice(response.body().as_slice()),
); );
@ -215,20 +217,20 @@ impl ToTokens for Api {
let error = &self.error; let error = &self.error;
let api = quote! { let api = quote! {
use ruma_api::exports::serde::de::Error as _; use ::ruma_api::exports::serde::de::Error as _;
use ruma_api::exports::serde::Deserialize as _; use ::ruma_api::exports::serde::Deserialize as _;
use ruma_api::Endpoint as _; use ::ruma_api::Endpoint as _;
use std::convert::TryInto as _; use std::convert::TryInto as _;
#[doc = #request_doc] #[doc = #request_doc]
#request_type #request_type
impl std::convert::TryFrom<ruma_api::exports::http::Request<Vec<u8>>> for #request_try_from_type { impl std::convert::TryFrom<::ruma_api::exports::http::Request<Vec<u8>>> for #request_try_from_type {
type Error = ruma_api::error::FromHttpRequestError; type Error = ::ruma_api::error::FromHttpRequestError;
#[allow(unused_variables)] #[allow(unused_variables)]
fn try_from(request: ruma_api::exports::http::Request<Vec<u8>>) -> Result<Self, Self::Error> { fn try_from(request: ::ruma_api::exports::http::Request<Vec<u8>>) -> Result<Self, Self::Error> {
#extract_request_path #extract_request_path
#extract_request_query #extract_request_query
#extract_request_headers #extract_request_headers
@ -243,17 +245,17 @@ impl ToTokens for Api {
} }
} }
impl std::convert::TryFrom<Request> for ruma_api::exports::http::Request<Vec<u8>> { impl std::convert::TryFrom<Request> for ::ruma_api::exports::http::Request<Vec<u8>> {
type Error = ruma_api::error::IntoHttpError; type Error = ::ruma_api::error::IntoHttpError;
#[allow(unused_mut, unused_variables)] #[allow(unused_mut, unused_variables)]
fn try_from(request: Request) -> Result<Self, Self::Error> { fn try_from(request: Request) -> Result<Self, Self::Error> {
let metadata = Request::METADATA; let metadata = Request::METADATA;
let path_and_query = #request_path_string + &#request_query_string; let path_and_query = #request_path_string + &#request_query_string;
let mut http_request = ruma_api::exports::http::Request::new(#request_body); let mut http_request = ::ruma_api::exports::http::Request::new(#request_body);
*http_request.method_mut() = ruma_api::exports::http::Method::#method; *http_request.method_mut() = ::ruma_api::exports::http::Method::#method;
*http_request.uri_mut() = ruma_api::exports::http::uri::Builder::new() *http_request.uri_mut() = ::ruma_api::exports::http::uri::Builder::new()
.path_and_query(path_and_query.as_str()) .path_and_query(path_and_query.as_str())
.build() .build()
// The ruma_api! macro guards against invalid path input, but if there are // The ruma_api! macro guards against invalid path input, but if there are
@ -269,13 +271,13 @@ impl ToTokens for Api {
#[doc = #response_doc] #[doc = #response_doc]
#response_type #response_type
impl std::convert::TryFrom<Response> for ruma_api::exports::http::Response<Vec<u8>> { impl std::convert::TryFrom<Response> for ::ruma_api::exports::http::Response<Vec<u8>> {
type Error = ruma_api::error::IntoHttpError; type Error = ::ruma_api::error::IntoHttpError;
#[allow(unused_variables)] #[allow(unused_variables)]
fn try_from(response: Response) -> Result<Self, Self::Error> { fn try_from(response: Response) -> Result<Self, Self::Error> {
let response = ruma_api::exports::http::Response::builder() let response = ::ruma_api::exports::http::Response::builder()
.header(ruma_api::exports::http::header::CONTENT_TYPE, "application/json") .header(::ruma_api::exports::http::header::CONTENT_TYPE, "application/json")
#serialize_response_headers #serialize_response_headers
.body(#body) .body(#body)
// Since we require header names to come from the `http::header` module, // Since we require header names to come from the `http::header` module,
@ -285,12 +287,12 @@ impl ToTokens for Api {
} }
} }
impl std::convert::TryFrom<ruma_api::exports::http::Response<Vec<u8>>> for #response_try_from_type { impl std::convert::TryFrom<::ruma_api::exports::http::Response<Vec<u8>>> for #response_try_from_type {
type Error = ruma_api::error::FromHttpResponseError<#error>; type Error = ::ruma_api::error::FromHttpResponseError<#error>;
#[allow(unused_variables)] #[allow(unused_variables)]
fn try_from( fn try_from(
response: ruma_api::exports::http::Response<Vec<u8>>, response: ::ruma_api::exports::http::Response<Vec<u8>>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
if response.status().as_u16() < 400 { if response.status().as_u16() < 400 {
#extract_response_headers #extract_response_headers
@ -301,22 +303,22 @@ impl ToTokens for Api {
#response_init_fields #response_init_fields
}) })
} else { } else {
match <#error as ruma_api::EndpointError>::try_from_response(response) { match <#error as ::ruma_api::EndpointError>::try_from_response(response) {
Ok(err) => Err(ruma_api::error::ServerError::Known(err).into()), Ok(err) => Err(::ruma_api::error::ServerError::Known(err).into()),
Err(response_err) => Err(ruma_api::error::ServerError::Unknown(response_err).into()) Err(response_err) => Err(::ruma_api::error::ServerError::Unknown(response_err).into())
} }
} }
} }
} }
impl ruma_api::Endpoint for Request { impl ::ruma_api::Endpoint for Request {
type Response = Response; type Response = Response;
type ResponseError = #error; type ResponseError = #error;
/// Metadata for the `#name` endpoint. /// Metadata for the `#name` endpoint.
const METADATA: ruma_api::Metadata = ruma_api::Metadata { const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata {
description: #description, description: #description,
method: ruma_api::exports::http::Method::#method, method: ::ruma_api::exports::http::Method::#method,
name: #name, name: #name,
path: #path, path: #path,
rate_limited: #rate_limited, rate_limited: #rate_limited,

View File

@ -33,8 +33,8 @@ impl Request {
quote! { quote! {
headers.append( headers.append(
ruma_api::exports::http::header::#header_name, ::ruma_api::exports::http::header::#header_name,
ruma_api::exports::http::header::HeaderValue::from_str(request.#field_name.as_ref())?, ::ruma_api::exports::http::header::HeaderValue::from_str(request.#field_name.as_ref())?,
); );
} }
}); });
@ -56,13 +56,13 @@ impl Request {
let header_name_string = header_name.to_string(); let header_name_string = header_name.to_string();
quote! { quote! {
#field_name: match headers.get(ruma_api::exports::http::header::#header_name) #field_name: match headers.get(::ruma_api::exports::http::header::#header_name)
.and_then(|v| v.to_str().ok()) { .and_then(|v| v.to_str().ok()) {
Some(header) => header.to_owned(), Some(header) => header.to_owned(),
None => { None => {
return Err( return Err(
ruma_api::error::RequestDeserializationError::new( ::ruma_api::error::RequestDeserializationError::new(
ruma_api::exports::serde_json::Error::missing_field( ::ruma_api::exports::serde_json::Error::missing_field(
#header_name_string #header_name_string
), ),
request, request,
@ -310,21 +310,34 @@ impl ToTokens for Request {
let request_body_struct = let request_body_struct =
if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) {
let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; let field = Field { ident: None, colon_token: None, ..body_field.field().clone() };
Some(quote! { (#field); }) let derive_deserialize = if body_field.has_wrap_incoming_attr() {
TokenStream::new()
} else {
quote!(::ruma_api::exports::serde::Deserialize)
};
Some((derive_deserialize, quote! { (#field); }))
} else if self.has_body_fields() { } else if self.has_body_fields() {
let fields = self.fields.iter().filter(|f| f.is_body()); let fields = self.fields.iter().filter(|f| f.is_body());
let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) {
TokenStream::new()
} else {
quote!(::ruma_api::exports::serde::Deserialize)
};
let fields = fields.map(RequestField::field); let fields = fields.map(RequestField::field);
Some(quote! { { #(#fields),* } })
Some((derive_deserialize, quote! { { #(#fields),* } }))
} else { } else {
None None
} }
.map(|def| { .map(|(derive_deserialize, def)| {
quote! { quote! {
/// Data in the request body. /// Data in the request body.
#[derive( #[derive(
Debug, Debug,
ruma_api::exports::serde::Deserialize, ::ruma_api::Outgoing,
ruma_api::exports::serde::Serialize, ::ruma_api::exports::serde::Serialize,
#derive_deserialize
)] )]
struct RequestBody #def struct RequestBody #def
} }
@ -337,8 +350,8 @@ impl ToTokens for Request {
/// Data in the request's query string. /// Data in the request's query string.
#[derive( #[derive(
Debug, Debug,
ruma_api::exports::serde::Deserialize, ::ruma_api::exports::serde::Deserialize,
ruma_api::exports::serde::Serialize, ::ruma_api::exports::serde::Serialize,
)] )]
struct RequestQuery(#field); struct RequestQuery(#field);
} }
@ -349,8 +362,8 @@ impl ToTokens for Request {
/// Data in the request's query string. /// Data in the request's query string.
#[derive( #[derive(
Debug, Debug,
ruma_api::exports::serde::Deserialize, ::ruma_api::exports::serde::Deserialize,
ruma_api::exports::serde::Serialize, ::ruma_api::exports::serde::Serialize,
)] )]
struct RequestQuery { struct RequestQuery {
#(#fields),* #(#fields),*
@ -361,7 +374,8 @@ impl ToTokens for Request {
}; };
let request = quote! { let request = quote! {
#[derive(Debug, Clone)] #[derive(Debug, Clone, ::ruma_api::Outgoing)]
#[incoming_no_deserialize]
pub struct Request #request_def pub struct Request #request_def
#request_body_struct #request_body_struct

View File

@ -56,9 +56,9 @@ impl Response {
} }
ResponseField::Header(_, header_name) => { ResponseField::Header(_, header_name) => {
quote_spanned! {span=> quote_spanned! {span=>
#field_name: ruma_api::try_deserialize!( #field_name: ::ruma_api::try_deserialize!(
response, response,
headers.remove(ruma_api::exports::http::header::#header_name) headers.remove(::ruma_api::exports::http::header::#header_name)
.expect("response missing expected header") .expect("response missing expected header")
.to_str() .to_str()
) )
@ -98,7 +98,7 @@ impl Response {
let span = field.span(); let span = field.span();
Some(quote_spanned! {span=> Some(quote_spanned! {span=>
.header(ruma_api::exports::http::header::#header_name, response.#field_name) .header(::ruma_api::exports::http::header::#header_name, response.#field_name)
}) })
} else { } else {
None None
@ -143,7 +143,7 @@ impl Response {
} }
}; };
quote!(ruma_api::exports::serde_json::to_vec(&#body)?) quote!(::ruma_api::exports::serde_json::to_vec(&#body)?)
} }
/// Gets the newtype body field, if this response has one. /// Gets the newtype body field, if this response has one.
@ -245,28 +245,44 @@ impl ToTokens for Response {
quote! { { #(#fields),* } } quote! { { #(#fields),* } }
}; };
let def = if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { let (derive_deserialize, def) =
if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) {
let field = Field { ident: None, colon_token: None, ..body_field.field().clone() }; let field = Field { ident: None, colon_token: None, ..body_field.field().clone() };
quote! { (#field); } let derive_deserialize = if body_field.has_wrap_incoming_attr() {
} else if self.has_body_fields() { TokenStream::new()
let fields = self.fields.iter().filter_map(|f| f.as_body_field());
quote!({ #(#fields),* })
} else { } else {
quote!({}) quote!(::ruma_api::exports::serde::Deserialize)
};
(derive_deserialize, quote! { (#field); })
} else if self.has_body_fields() {
let fields = self.fields.iter().filter(|f| f.is_body());
let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) {
TokenStream::new()
} else {
quote!(::ruma_api::exports::serde::Deserialize)
};
let fields = fields.map(ResponseField::field);
(derive_deserialize, quote!({ #(#fields),* }))
} else {
(TokenStream::new(), quote!({}))
}; };
let response_body_struct = quote! { let response_body_struct = quote! {
/// Data in the response body. /// Data in the response body.
#[derive( #[derive(
Debug, Debug,
ruma_api::exports::serde::Deserialize, ::ruma_api::Outgoing,
ruma_api::exports::serde::Serialize, ::ruma_api::exports::serde::Serialize,
#derive_deserialize
)] )]
struct ResponseBody #def struct ResponseBody #def
}; };
let response = quote! { let response = quote! {
#[derive(Debug, Clone)] #[derive(Debug, Clone, ::ruma_api::Outgoing)]
#[incoming_no_deserialize]
pub struct Response #response_def pub struct Response #response_def
#response_body_struct #response_body_struct

View File

@ -0,0 +1,162 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use syn::{
parse_quote, AngleBracketedGenericArguments, Attribute, Data, DeriveInput, Fields,
GenericArgument, GenericParam, Generics, ImplGenerics, PathArguments, Type, TypeGenerics,
TypePath, TypeReference, TypeSlice,
};
enum StructKind {
Struct,
Tuple,
}
pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result<TokenStream> {
let derive_deserialize = if no_deserialize_in_attrs(&input.attrs) {
TokenStream::new()
} else {
quote!(::ruma_api::exports::serde::Deserialize)
};
let (mut fields, struct_kind): (Vec<_>, _) = match input.data {
Data::Enum(_) | Data::Union(_) => {
panic!("#[derive(Outgoing)] is only supported for structs")
}
Data::Struct(s) => match s.fields {
Fields::Named(fs) => (fs.named.into_iter().collect(), StructKind::Struct),
Fields::Unnamed(fs) => (fs.unnamed.into_iter().collect(), StructKind::Tuple),
Fields::Unit => return Ok(impl_outgoing_with_incoming_self(input.ident)),
},
};
let mut any_attribute = false;
for field in &mut fields {
if strip_lifetimes(&mut field.ty) {
any_attribute = true;
}
}
if !any_attribute {
return Ok(impl_outgoing_with_incoming_self(input.ident));
}
let original_ident = &input.ident;
let (original_impl_gen, original_ty_gen, _) = input.generics.split_for_impl();
let vis = input.vis;
let doc = format!("'Incoming' variant of [{ty}](struct.{ty}.html).", ty = &input.ident);
let incoming_ident = format_ident!("Incoming{}", original_ident, span = Span::call_site());
let mut gen_copy = input.generics.clone();
let (impl_gen, ty_gen) = split_for_impl_lifetime_less(&mut gen_copy);
let struct_def = match struct_kind {
StructKind::Struct => quote! { { #(#fields,)* } },
StructKind::Tuple => quote! { ( #(#fields,)* ); },
};
Ok(quote! {
#[doc = #doc]
#[derive(Debug, #derive_deserialize)]
#vis struct #incoming_ident #ty_gen #struct_def
impl #original_impl_gen ::ruma_api::Outgoing for #original_ident #original_ty_gen {
type Incoming = #incoming_ident #impl_gen;
}
})
}
fn no_deserialize_in_attrs(attrs: &[Attribute]) -> bool {
attrs.iter().any(|attr| attr.path.is_ident("incoming_no_deserialize"))
}
fn impl_outgoing_with_incoming_self(ident: Ident) -> TokenStream {
quote! {
impl ::ruma_api::Outgoing for #ident {
type Incoming = Self;
}
}
}
fn split_for_impl_lifetime_less(generics: &mut Generics) -> (ImplGenerics, TypeGenerics) {
generics.params = generics
.params
.clone()
.into_iter()
.filter(|param| !matches!(param, GenericParam::Lifetime(_)))
.collect();
let (impl_gen, ty_gen, _) = generics.split_for_impl();
(impl_gen, ty_gen)
}
fn strip_lifetimes(field_type: &mut Type) -> bool {
match field_type {
// T<'a> -> IncomingT
// The IncomingT has to be declared by the user of this derive macro.
Type::Path(TypePath { path, .. }) => {
let mut has_lifetimes = false;
for seg in &mut path.segments {
// strip generic lifetimes
if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
args, ..
}) = &mut seg.arguments
{
*args = args
.clone()
.into_iter()
.filter(|arg| {
if let GenericArgument::Lifetime(_) = arg {
has_lifetimes = true;
false
} else {
true
}
})
.collect();
}
}
if has_lifetimes {
if let Some(name) = path.segments.last_mut() {
let incoming_ty_ident = format_ident!("Incoming{}", name.ident);
name.ident = incoming_ty_ident;
}
}
has_lifetimes
}
Type::Reference(TypeReference { elem, .. }) => match &mut **elem {
Type::Path(ty_path) => {
let TypePath { path, .. } = ty_path;
let segs = path
.segments
.clone()
.into_iter()
.map(|seg| seg.ident.to_string())
.collect::<Vec<_>>();
if path.is_ident("str") {
// &str -> String
*field_type = parse_quote! { String };
} else if segs.contains(&"DeviceId".into()) || segs.contains(&"ServerName".into()) {
// The identifiers that need to be boxed `Box<T>` since they are DST's.
*field_type = parse_quote! { Box<#path> };
} else {
// &T -> T
*field_type = Type::Path(ty_path.clone());
}
true
}
// &[T] -> Vec<T>
Type::Slice(TypeSlice { elem, .. }) => {
// Recursively strip the lifetimes of the slice's elements.
strip_lifetimes(&mut *elem);
*field_type = parse_quote! { Vec<#elem> };
true
}
_ => false,
},
_ => false,
}
}

View File

@ -15,11 +15,15 @@ use std::convert::TryFrom as _;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::ToTokens; use quote::ToTokens;
use syn::parse_macro_input; use syn::{parse_macro_input, DeriveInput};
use self::api::{Api, RawApi}; use self::{
api::{Api, RawApi},
derive_outgoing::expand_derive_outgoing,
};
mod api; mod api;
mod derive_outgoing;
mod util; mod util;
#[proc_macro] #[proc_macro]
@ -30,3 +34,48 @@ pub fn ruma_api(input: TokenStream) -> TokenStream {
Err(err) => err.to_compile_error().into(), Err(err) => err.to_compile_error().into(),
} }
} }
/// Derive the `Outgoing` trait, possibly generating an 'Incoming' version of the struct this
/// derive macro is used on. Specifically, if no `#[wrap_incoming]` attribute is used on any of the
/// fields of the struct, this simple implementation will be generated:
///
/// ```ignore
/// impl Outgoing for MyType {
/// type Incoming = Self;
/// }
/// ```
///
/// If, however, `#[wrap_incoming]` is used (which is the only reason you should ever use this
/// derive macro manually), a new struct `IncomingT` (where `T` is the type this derive is used on)
/// is generated, with all of the fields with `#[wrap_incoming]` replaced:
///
/// ```ignore
/// #[derive(Outgoing)]
/// struct MyType {
/// pub foo: Foo,
/// #[wrap_incoming]
/// pub bar: Bar,
/// #[wrap_incoming(Baz)]
/// pub baz: Option<Baz>,
/// #[wrap_incoming(with EventResult)]
/// pub x: XEvent,
/// #[wrap_incoming(YEvent with EventResult)]
/// pub ys: Vec<YEvent>,
/// }
///
/// // generated
/// struct IncomingMyType {
/// pub foo: Foo,
/// pub bar: IncomingBar,
/// pub baz: Option<IncomingBaz>,
/// pub x: EventResult<XEvent>,
/// pub ys: Vec<EventResult<YEvent>>,
/// }
/// ```
// TODO: Make it clear that `#[wrap_incoming]` and `#[wrap_incoming(Type)]` without the "with" part
// are (only) useful for fallible deserialization of nested structures.
#[proc_macro_derive(Outgoing, attributes(wrap_incoming, incoming_no_deserialize))]
pub fn derive_outgoing(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand_derive_outgoing(input).unwrap_or_else(|err| err.to_compile_error()).into()
}

View File

@ -194,8 +194,17 @@ use http::Method;
/// } /// }
/// } /// }
/// ``` /// ```
///
/// ## Fallible deserialization
///
/// All request and response types also derive [`Outgoing`][Outgoing]. As such, to allow fallible
/// deserialization, you can use the `#[wrap_incoming]` attribute. For details, see the
/// documentation for [the derive macro](derive.Outgoing.html).
// TODO: Explain the concept of fallible deserialization before jumping to `ruma_api::Outgoing`
pub use ruma_api_macros::ruma_api; pub use ruma_api_macros::ruma_api;
pub use ruma_api_macros::Outgoing;
pub mod error; pub mod error;
/// This module is used to support the generated code from ruma-api-macros. /// This module is used to support the generated code from ruma-api-macros.
/// It is not considered part of ruma-api's public API. /// It is not considered part of ruma-api's public API.
@ -210,6 +219,18 @@ pub mod exports {
use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError}; use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError};
/// A type that can be sent to another party that understands the matrix protocol. If any of the
/// fields of `Self` don't implement serde's `Deserialize`, you can derive this trait to generate a
/// corresponding 'Incoming' type that supports deserialization. This is useful for things like
/// ruma_events' `EventResult` type. For more details, see the [derive macro's documentation][doc].
///
/// [doc]: derive.Outgoing.html
// TODO: Better explain how this trait relates to serde's traits
pub trait Outgoing {
/// The 'Incoming' variant of `Self`.
type Incoming;
}
/// Gives users the ability to define their own serializable/deserializable errors. /// Gives users the ability to define their own serializable/deserializable errors.
pub trait EndpointError: Sized { pub trait EndpointError: Sized {
/// Tries to construct `Self` from an `http::Response`. /// Tries to construct `Self` from an `http::Response`.
@ -224,14 +245,16 @@ pub trait EndpointError: Sized {
/// A Matrix API endpoint. /// A Matrix API endpoint.
/// ///
/// The type implementing this trait contains any data needed to make a request to the endpoint. /// The type implementing this trait contains any data needed to make a request to the endpoint.
pub trait Endpoint: pub trait Endpoint: Outgoing + TryInto<http::Request<Vec<u8>>, Error = IntoHttpError>
TryInto<http::Request<Vec<u8>>, Error = IntoHttpError> where
+ TryFrom<http::Request<Vec<u8>>, Error = FromHttpRequestError> <Self as Outgoing>::Incoming: TryFrom<http::Request<Vec<u8>>, Error = FromHttpRequestError>,
<Self::Response as Outgoing>::Incoming: TryFrom<
http::Response<Vec<u8>>,
Error = FromHttpResponseError<<Self as Endpoint>::ResponseError>,
>,
{ {
/// Data returned in a successful response from the endpoint. /// Data returned in a successful response from the endpoint.
type Response: TryInto<http::Response<Vec<u8>>, Error = IntoHttpError> type Response: Outgoing + TryInto<http::Response<Vec<u8>>, Error = IntoHttpError>;
+ TryFrom<http::Response<Vec<u8>>, Error = FromHttpResponseError<Self::ResponseError>>;
/// Error type returned when response from endpoint fails. /// Error type returned when response from endpoint fails.
type ResponseError: EndpointError; type ResponseError: EndpointError;
@ -242,7 +265,15 @@ pub trait Endpoint:
/// A Matrix API endpoint that doesn't require authentication. /// A Matrix API endpoint that doesn't require authentication.
/// ///
/// This marker trait is to indicate that a type implementing `Endpoint` doesn't require any authentication. /// This marker trait is to indicate that a type implementing `Endpoint` doesn't require any authentication.
pub trait NonAuthEndpoint: Endpoint {} pub trait NonAuthEndpoint: Endpoint
where
<Self as Outgoing>::Incoming: TryFrom<http::Request<Vec<u8>>, Error = FromHttpRequestError>,
<Self::Response as Outgoing>::Incoming: TryFrom<
http::Response<Vec<u8>>,
Error = FromHttpResponseError<<Self as Endpoint>::ResponseError>,
>,
{
}
/// Metadata about an API endpoint. /// Metadata about an API endpoint.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -302,7 +333,7 @@ mod tests {
FromHttpRequestError, FromHttpResponseError, IntoHttpError, FromHttpRequestError, FromHttpResponseError, IntoHttpError,
RequestDeserializationError, ServerError, Void, RequestDeserializationError, ServerError, Void,
}, },
Endpoint, Metadata, Endpoint, Metadata, Outgoing,
}; };
/// A request to create a new room alias. /// A request to create a new room alias.
@ -312,6 +343,10 @@ mod tests {
pub room_alias: RoomAliasId, // path pub room_alias: RoomAliasId, // path
} }
impl Outgoing for Request {
type Incoming = Self;
}
impl Endpoint for Request { impl Endpoint for Request {
type Response = Response; type Response = Response;
type ResponseError = Void; type ResponseError = Void;
@ -394,6 +429,10 @@ mod tests {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Response; pub struct Response;
impl Outgoing for Response {
type Incoming = Self;
}
impl TryFrom<http::Response<Vec<u8>>> for Response { impl TryFrom<http::Response<Vec<u8>>> for Response {
type Error = FromHttpResponseError<Void>; type Error = FromHttpResponseError<Void>;

View File

@ -0,0 +1,25 @@
use ruma_api::Outgoing;
use ruma_identifiers::UserId;
#[allow(unused)]
pub struct Thing<'t, T> {
some: &'t str,
t: &'t T,
}
#[derive(Debug)]
pub struct IncomingThing<T> {
some: String,
t: T,
}
#[derive(Outgoing)]
#[incoming_no_deserialize]
pub struct Request<'a, T> {
pub abc: &'a str,
pub thing: Thing<'a, T>,
pub device_id: &'a ::ruma_identifiers::DeviceId,
pub user_id: &'a UserId,
pub bytes: &'a [u8],
pub recursive: &'a [Thing<'a, T>],
}