Remove Outgoing trait

we won't need it anymore once `EventResult` is replaced with `EventJson`
This commit is contained in:
Jonas Platte 2020-04-22 12:54:47 +02:00
parent 620ca0ebcf
commit df7914de1a
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
9 changed files with 34 additions and 432 deletions

View File

@ -26,7 +26,7 @@ serde_urlencoded = "0.6.1"
strum = "0.18.0" strum = "0.18.0"
[dev-dependencies] [dev-dependencies]
ruma-events = "0.19.0" ruma-events = { git = "https://github.com/ruma/ruma-events", branch = "event-json" }
[features] [features]
default = ["with-ruma-api-macros"] default = ["with-ruma-api-macros"]

View File

@ -298,7 +298,7 @@ impl ToTokens for Api {
let extract_request_body = let extract_request_body =
if self.request.has_body_fields() || self.request.newtype_body_field().is_some() { if self.request.has_body_fields() || self.request.newtype_body_field().is_some() {
quote! { quote! {
let request_body: <RequestBody as ruma_api::Outgoing>::Incoming = let request_body: RequestBody =
match ruma_api::exports::serde_json::from_slice(request.body().as_slice()) { match ruma_api::exports::serde_json::from_slice(request.body().as_slice()) {
Ok(body) => body, Ok(body) => body,
Err(err) => { Err(err) => {
@ -368,7 +368,7 @@ impl ToTokens for Api {
|| self.response.newtype_body_field().is_some() || self.response.newtype_body_field().is_some()
{ {
quote! { quote! {
let response_body: <ResponseBody as ruma_api::Outgoing>::Incoming = let response_body: ResponseBody =
match ruma_api::exports::serde_json::from_slice(response.body().as_slice()) { match ruma_api::exports::serde_json::from_slice(response.body().as_slice()) {
Ok(body) => body, Ok(body) => body,
Err(err) => { Err(err) => {

View File

@ -310,34 +310,21 @@ 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() };
let derive_deserialize = if body_field.has_wrap_incoming_attr() { Some(quote! { (#field); })
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(|(derive_deserialize, def)| { .map(|def| {
quote! { quote! {
/// Data in the request body. /// Data in the request body.
#[derive( #[derive(
Debug, Debug,
ruma_api::Outgoing, ruma_api::exports::serde::Deserialize,
ruma_api::exports::serde::Serialize, ruma_api::exports::serde::Serialize,
#derive_deserialize
)] )]
struct RequestBody #def struct RequestBody #def
} }
@ -374,8 +361,7 @@ impl ToTokens for Request {
}; };
let request = quote! { let request = quote! {
#[derive(Debug, Clone, ruma_api::Outgoing)] #[derive(Debug, Clone)]
#[incoming_no_deserialize]
pub struct Request #request_def pub struct Request #request_def
#request_body_struct #request_body_struct

View File

@ -244,44 +244,28 @@ impl ToTokens for Response {
quote! { { #(#fields),* } } quote! { { #(#fields),* } }
}; };
let (derive_deserialize, def) = let def = 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() }; 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());
} else { quote!({ #(#fields),* })
quote!(ruma_api::exports::serde::Deserialize) } else {
}; quote!({})
};
(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::Outgoing, ruma_api::exports::serde::Deserialize,
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, ruma_api::Outgoing)] #[derive(Debug, Clone)]
#[incoming_no_deserialize]
pub struct Response #response_def pub struct Response #response_def
#response_body_struct #response_body_struct

View File

@ -1,214 +0,0 @@
use std::mem;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::{
parse_quote, punctuated::Pair, spanned::Spanned, Attribute, Data, DeriveInput, Fields,
GenericArgument, Path, PathArguments, Type, TypePath,
};
mod wrap_incoming;
use wrap_incoming::Meta;
enum StructKind {
Struct,
Tuple,
}
pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result<TokenStream> {
if !input.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
input.generics,
"derive(Outgoing) doesn't currently support types with generics!",
));
}
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_pairs().map(Pair::into_value).collect(), StructKind::Struct)
}
Fields::Unnamed(fs) => {
(fs.unnamed.into_pairs().map(Pair::into_value).collect(), StructKind::Tuple)
}
Fields::Unit => return Ok(impl_outgoing_with_incoming_self(input.ident)),
},
};
let mut any_attribute = false;
for field in &mut fields {
let mut field_meta = None;
let mut remaining_attrs = Vec::new();
for attr in mem::replace(&mut field.attrs, Vec::new()) {
if let Some(meta) = Meta::from_attribute(&attr)? {
if field_meta.is_some() {
return Err(syn::Error::new_spanned(
attr,
"duplicate #[wrap_incoming] attribute",
));
}
field_meta = Some(meta);
any_attribute = true;
} else {
remaining_attrs.push(attr);
}
}
field.attrs = remaining_attrs;
if let Some(attr) = field_meta {
if let Some(type_to_wrap) = attr.type_to_wrap {
wrap_generic_arg(&type_to_wrap, &mut field.ty, attr.wrapper_type.as_ref())?;
} else {
wrap_ty(&mut field.ty, attr.wrapper_type)?;
}
}
}
if !any_attribute {
return Ok(impl_outgoing_with_incoming_self(input.ident));
}
let vis = input.vis;
let doc = format!("'Incoming' variant of [{ty}](struct.{ty}.html).", ty = input.ident);
let original_ident = input.ident;
let incoming_ident = format_ident!("Incoming{}", original_ident, span = Span::call_site());
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 #struct_def
impl ruma_api::Outgoing for #original_ident {
type Incoming = #incoming_ident;
}
})
}
fn no_deserialize_in_attrs(attrs: &[Attribute]) -> bool {
for attr in attrs {
match &attr.path {
Path { leading_colon: None, segments }
if segments.len() == 1 && segments[0].ident == "incoming_no_deserialize" =>
{
return true
}
_ => {}
}
}
false
}
fn impl_outgoing_with_incoming_self(ident: Ident) -> TokenStream {
quote! {
impl ruma_api::Outgoing for #ident {
type Incoming = Self;
}
}
}
fn wrap_ty(ty: &mut Type, path: Option<Path>) -> syn::Result<()> {
if let Some(wrap_ty) = path {
*ty = parse_quote!(#wrap_ty<#ty>);
} else {
match ty {
Type::Path(TypePath { path, .. }) => {
let ty_ident = &mut path.segments.last_mut().unwrap().ident;
let ident = format_ident!("Incoming{}", ty_ident, span = Span::call_site());
*ty_ident = parse_quote!(#ident);
}
_ => return Err(syn::Error::new_spanned(ty, "Can't wrap this type")),
}
}
Ok(())
}
fn wrap_generic_arg(type_to_wrap: &Type, of: &mut Type, with: Option<&Path>) -> syn::Result<()> {
let mut span = None;
wrap_generic_arg_impl(type_to_wrap, of, with, &mut span)?;
if span.is_some() {
Ok(())
} else {
Err(syn::Error::new_spanned(
of,
format!(
"Couldn't find generic argument `{}` in this type",
type_to_wrap.to_token_stream()
),
))
}
}
fn wrap_generic_arg_impl(
type_to_wrap: &Type,
of: &mut Type,
with: Option<&Path>,
span: &mut Option<Span>,
) -> syn::Result<()> {
// TODO: Support things like array types?
let ty_path = match of {
Type::Path(TypePath { path, .. }) => path,
_ => return Ok(()),
};
let args = match &mut ty_path.segments.last_mut().unwrap().arguments {
PathArguments::AngleBracketed(ab) => &mut ab.args,
_ => return Ok(()),
};
for arg in args.iter_mut() {
let ty = match arg {
GenericArgument::Type(ty) => ty,
_ => continue,
};
if ty == type_to_wrap {
if let Some(s) = span {
let mut error = syn::Error::new(
*s,
format!(
"`{}` found multiple times, this is not currently supported",
type_to_wrap.to_token_stream()
),
);
error.combine(syn::Error::new_spanned(ty, "second occurrence"));
return Err(error);
}
*span = Some(ty.span());
if let Some(wrapper_type) = with {
*ty = parse_quote!(#wrapper_type<#ty>);
} else if let Type::Path(TypePath { path, .. }) = ty {
let ty_ident = &mut path.segments.last_mut().unwrap().ident;
let ident = format_ident!("Incoming{}", ty_ident, span = Span::call_site());
*ty_ident = parse_quote!(#ident);
} else {
return Err(syn::Error::new_spanned(ty, "Can't wrap this type"));
}
} else {
wrap_generic_arg_impl(type_to_wrap, ty, with, span)?;
}
}
Ok(())
}

View File

@ -1,58 +0,0 @@
use syn::{
parse::{Parse, ParseStream},
Ident, Path, Type,
};
mod kw {
use syn::custom_keyword;
custom_keyword!(with);
}
/// The inside of a `#[wrap_incoming]` attribute
#[derive(Default)]
pub struct Meta {
pub type_to_wrap: Option<Type>,
pub wrapper_type: Option<Path>,
}
impl Meta {
/// Check if the given attribute is a wrap_incoming attribute. If it is, parse it.
pub fn from_attribute(attr: &syn::Attribute) -> syn::Result<Option<Self>> {
if attr.path.is_ident("wrap_incoming") {
if attr.tokens.is_empty() {
Ok(Some(Self::default()))
} else {
attr.parse_args().map(Some)
}
} else {
Ok(None)
}
}
}
impl Parse for Meta {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut type_to_wrap = None;
let mut wrapper_type = try_parse_wrapper_type(input)?;
if wrapper_type.is_none() && input.peek(Ident) {
type_to_wrap = Some(input.parse()?);
wrapper_type = try_parse_wrapper_type(input)?;
}
if input.is_empty() {
Ok(Self { type_to_wrap, wrapper_type })
} else {
Err(input.error("expected end of attribute args"))
}
}
}
fn try_parse_wrapper_type(input: ParseStream) -> syn::Result<Option<Path>> {
if input.peek(kw::with) {
input.parse::<kw::with>()?;
Ok(Some(input.parse()?))
} else {
Ok(None)
}
}

View File

@ -14,15 +14,11 @@ use std::convert::TryFrom as _;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::ToTokens; use quote::ToTokens;
use syn::{parse_macro_input, DeriveInput}; use syn::parse_macro_input;
use self::{ use self::api::{Api, RawApi};
api::{Api, RawApi},
derive_outgoing::expand_derive_outgoing,
};
mod api; mod api;
mod derive_outgoing;
#[proc_macro] #[proc_macro]
pub fn ruma_api(input: TokenStream) -> TokenStream { pub fn ruma_api(input: TokenStream) -> TokenStream {
@ -32,48 +28,3 @@ 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

@ -191,19 +191,9 @@ 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`
#[cfg(feature = "with-ruma-api-macros")] #[cfg(feature = "with-ruma-api-macros")]
pub use ruma_api_macros::ruma_api; pub use ruma_api_macros::ruma_api;
#[cfg(feature = "with-ruma-api-macros")]
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.
@ -219,18 +209,6 @@ 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`.
@ -245,16 +223,14 @@ 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: Outgoing + TryInto<http::Request<Vec<u8>>, Error = IntoHttpError> pub trait Endpoint:
where TryInto<http::Request<Vec<u8>>, Error = IntoHttpError>
<Self as Outgoing>::Incoming: TryFrom<http::Request<Vec<u8>>, Error = FromHttpRequestError>, + 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: Outgoing + TryInto<http::Response<Vec<u8>>, Error = IntoHttpError>; type Response: 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;
@ -300,7 +276,7 @@ mod tests {
FromHttpRequestError, FromHttpResponseError, IntoHttpError, FromHttpRequestError, FromHttpResponseError, IntoHttpError,
RequestDeserializationError, ServerError, Void, RequestDeserializationError, ServerError, Void,
}, },
Endpoint, Metadata, Outgoing, Endpoint, Metadata,
}; };
/// A request to create a new room alias. /// A request to create a new room alias.
@ -310,10 +286,6 @@ 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,10 +366,6 @@ 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

@ -1,7 +1,6 @@
pub mod some_endpoint { pub mod some_endpoint {
use ruma_api::{ruma_api, Outgoing}; use ruma_api::ruma_api;
use ruma_events::{collections::all, sticker::StickerEvent, tag::TagEvent, EventResult}; use ruma_events::{collections::all, tag::TagEvent, EventJson};
use serde::Serialize;
ruma_api! { ruma_api! {
metadata { metadata {
@ -43,27 +42,13 @@ pub mod some_endpoint {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub optional_flag: Option<bool>, pub optional_flag: Option<bool>,
// This is how you usually use `#[wrap_incoming]` with event types // Use `EventJson` instead of the actual event to allow additional fields to be sent...
#[wrap_incoming(with EventResult)] pub event: EventJson<TagEvent>,
pub event: TagEvent,
// Same for lists of events // ... and to allow unknown events when the endpoint deals with event collections.
#[wrap_incoming(all::RoomEvent with EventResult)] pub list_of_events: Vec<EventJson<all::RoomEvent>>,
pub list_of_events: Vec<all::RoomEvent>,
// This is how `#[wrap_incoming]` is used with nested `EventResult`s
#[wrap_incoming]
pub object: ObjectContainingEvents,
} }
} }
#[derive(Clone, Debug, Serialize, Outgoing)]
pub struct ObjectContainingEvents {
#[wrap_incoming(TagEvent with EventResult)]
pub event_list_1: Vec<TagEvent>,
#[wrap_incoming(StickerEvent with EventResult)]
pub event_list_2: Vec<StickerEvent>,
}
} }
pub mod newtype_body_endpoint { pub mod newtype_body_endpoint {