Enable deserialization of unsuccessful responses

Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
This commit is contained in:
Ragotzy.devin 2020-03-16 20:07:12 -04:00 committed by GitHub
parent 6f5e25cb7d
commit 6a0ab987b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 105 additions and 41 deletions

View File

@ -7,7 +7,7 @@ use quote::{quote, ToTokens};
use syn::{
braced,
parse::{Parse, ParseStream},
Field, FieldValue, Ident, Token,
Field, FieldValue, Ident, Token, Type,
};
mod attribute;
@ -32,6 +32,8 @@ pub struct Api {
request: Request,
/// The `response` section of the macro.
response: Response,
/// The `error` section of the macro.
error: Type,
}
impl TryFrom<RawApi> for Api {
@ -42,6 +44,9 @@ impl TryFrom<RawApi> for Api {
metadata: raw_api.metadata.try_into()?,
request: raw_api.request.try_into()?,
response: raw_api.response.try_into()?,
error: raw_api
.error
.map_or(syn::parse_str::<Type>("ruma_api::error::Void").unwrap(), |err| err.ty),
};
let newtype_body_field = res.request.newtype_body_field();
@ -398,6 +403,8 @@ impl ToTokens for Api {
);
let response_doc = format!("Data in the response from the `{}` API endpoint.", name);
let error = &self.error;
let api = quote! {
use ruma_api::exports::serde::de::Error as _;
use ruma_api::exports::serde::Deserialize as _;
@ -473,7 +480,7 @@ impl ToTokens for Api {
}
impl std::convert::TryFrom<ruma_api::exports::http::Response<Vec<u8>>> for #response_try_from_type {
type Error = ruma_api::error::FromHttpResponseError;
type Error = ruma_api::error::FromHttpResponseError<#error>;
#[allow(unused_variables)]
fn try_from(
@ -488,13 +495,17 @@ impl ToTokens for Api {
#response_init_fields
})
} else {
Err(ruma_api::error::ServerError::new(response).into())
match <#error as ruma_api::EndpointError>::try_from_response(response) {
Ok(err) => Err(ruma_api::error::ServerError::Known(err).into()),
Err(response_err) => Err(ruma_api::error::ServerError::Unknown(response_err).into())
}
}
}
}
impl ruma_api::Endpoint for Request {
type Response = Response;
type ResponseError = #error;
/// Metadata for the `#name` endpoint.
const METADATA: ruma_api::Metadata = ruma_api::Metadata {
@ -519,6 +530,7 @@ mod kw {
custom_keyword!(metadata);
custom_keyword!(request);
custom_keyword!(response);
custom_keyword!(error);
}
/// The entire `ruma_api!` macro structure directly as it appears in the source code..
@ -529,11 +541,18 @@ pub struct RawApi {
pub request: RawRequest,
/// The `response` section of the macro.
pub response: RawResponse,
/// The `error` section of the macro.
pub error: Option<RawErrorType>,
}
impl Parse for RawApi {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
Ok(Self { metadata: input.parse()?, request: input.parse()?, response: input.parse()? })
Ok(Self {
metadata: input.parse()?,
request: input.parse()?,
response: input.parse()?,
error: input.parse().ok(),
})
}
}
@ -599,3 +618,18 @@ impl Parse for RawResponse {
})
}
}
pub struct RawErrorType {
pub error_kw: kw::error,
pub ty: Type,
}
impl Parse for RawErrorType {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let error_kw = input.parse::<kw::error>()?;
input.parse::<Token![:]>()?;
let ty = input.parse()?;
Ok(Self { error_kw, ty })
}
}

View File

@ -4,6 +4,18 @@
use std::fmt::{self, Display, Formatter};
// FIXME when `!` becomes stable use it
/// Default `ResponseError` for `ruma_api!` macro
#[derive(Clone, Copy, Debug)]
pub struct Void;
impl crate::EndpointError for Void {
fn try_from_response(
response: http::Response<Vec<u8>>,
) -> Result<Self, ResponseDeserializationError> {
Err(ResponseDeserializationError::from_response(response))
}
}
/// An error when converting one of ruma's endpoint-specific request or response
/// types to the corresponding http type.
#[derive(Debug)]
@ -92,14 +104,14 @@ impl std::error::Error for RequestDeserializationError {}
/// response types.
#[derive(Debug)]
#[non_exhaustive]
pub enum FromHttpResponseError {
pub enum FromHttpResponseError<E> {
/// Deserialization failed
Deserialization(ResponseDeserializationError),
/// The server returned a non-success status
Http(ServerError),
Http(ServerError<E>),
}
impl Display for FromHttpResponseError {
impl<E: Display> Display for FromHttpResponseError<E> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
@ -108,13 +120,13 @@ impl Display for FromHttpResponseError {
}
}
impl From<ServerError> for FromHttpResponseError {
fn from(err: ServerError) -> Self {
impl<E> From<ServerError<E>> for FromHttpResponseError<E> {
fn from(err: ServerError<E>) -> Self {
Self::Http(err)
}
}
impl From<ResponseDeserializationError> for FromHttpResponseError {
impl<E> From<ResponseDeserializationError> for FromHttpResponseError<E> {
fn from(err: ResponseDeserializationError) -> Self {
Self::Deserialization(err)
}
@ -123,7 +135,7 @@ impl From<ResponseDeserializationError> for FromHttpResponseError {
/// An error that occurred when trying to deserialize a response.
#[derive(Debug)]
pub struct ResponseDeserializationError {
inner: DeserializationError,
inner: Option<DeserializationError>,
http_response: http::Response<Vec<u8>>,
}
@ -135,13 +147,25 @@ impl ResponseDeserializationError {
inner: impl Into<DeserializationError>,
http_response: http::Response<Vec<u8>>,
) -> Self {
Self { inner: inner.into(), http_response }
Self { inner: Some(inner.into()), http_response }
}
/// This method is public so it is accessible from `ruma_api!` generated
/// code. It is not considered part of ruma-api's public API.
/// Creates an Error from a `http::Response`.
#[doc(hidden)]
pub fn from_response(http_response: http::Response<Vec<u8>>) -> Self {
Self { http_response, inner: None }
}
}
impl Display for ResponseDeserializationError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.inner, f)
if let Some(ref inner) = self.inner {
Display::fmt(inner, f)
} else {
Display::fmt("deserialization error, no error specified", f)
}
}
}
@ -149,36 +173,24 @@ impl std::error::Error for ResponseDeserializationError {}
/// An error was reported by the server (HTTP status code 4xx or 5xx)
#[derive(Debug)]
pub struct ServerError {
http_response: http::Response<Vec<u8>>,
pub enum ServerError<E> {
/// An error that is expected to happen under certain circumstances and
/// that has a well-defined structure
Known(E),
/// An error of unexpected type of structure
Unknown(ResponseDeserializationError),
}
impl ServerError {
/// This method is public so it is accessible from `ruma_api!` generated
/// code. It is not considered part of ruma-api's public API.
#[doc(hidden)]
pub fn new(http_response: http::Response<Vec<u8>>) -> Self {
Self { http_response }
}
/// Get the HTTP response without parsing its contents.
pub fn into_raw_reponse(self) -> http::Response<Vec<u8>> {
self.http_response
}
}
impl Display for ServerError {
impl<E: Display> Display for ServerError<E> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.http_response.status().canonical_reason() {
Some(reason) => {
write!(f, "HTTP status {} {}", self.http_response.status().as_str(), reason)
}
None => write!(f, "HTTP status {}", self.http_response.status().as_str()),
match self {
ServerError::Known(e) => Display::fmt(e, f),
ServerError::Unknown(res_err) => Display::fmt(res_err, f),
}
}
}
impl std::error::Error for ServerError {}
impl<E: std::error::Error> std::error::Error for ServerError<E> {}
#[derive(Debug)]
enum SerializationError {

View File

@ -232,17 +232,32 @@ pub trait Outgoing {
type Incoming;
}
/// Gives users the ability to define their own serializable/deserializable errors.
pub trait EndpointError: Sized {
/// Tries to construct `Self` from an `http::Response`.
///
/// This will always return `Err` variant when no `error` field is defined in
/// the `ruma_api` macro.
fn try_from_response(
response: http::Response<Vec<u8>>,
) -> Result<Self, error::ResponseDeserializationError>;
}
/// A Matrix API 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>
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::Response as Outgoing>::Incoming: TryFrom<
http::Response<Vec<u8>>,
Error = FromHttpResponseError<<Self as Endpoint>::ResponseError>,
>,
{
/// Data returned in a successful response from the endpoint.
type Response: Outgoing + TryInto<http::Response<Vec<u8>>, Error = IntoHttpError>;
/// Error type returned when response from endpoint fails.
type ResponseError: EndpointError;
/// Metadata about the endpoint.
const METADATA: Metadata;
@ -284,7 +299,7 @@ mod tests {
use crate::{
error::{
FromHttpRequestError, FromHttpResponseError, IntoHttpError,
RequestDeserializationError, ServerError,
RequestDeserializationError, ServerError, Void,
},
Endpoint, Metadata, Outgoing,
};
@ -302,6 +317,7 @@ mod tests {
impl Endpoint for Request {
type Response = Response;
type ResponseError = Void;
const METADATA: Metadata = Metadata {
description: "Add an alias to a room.",
@ -384,13 +400,15 @@ mod tests {
}
impl TryFrom<http::Response<Vec<u8>>> for Response {
type Error = FromHttpResponseError;
type Error = FromHttpResponseError<Void>;
fn try_from(http_response: http::Response<Vec<u8>>) -> Result<Response, Self::Error> {
if http_response.status().as_u16() < 400 {
Ok(Response)
} else {
Err(FromHttpResponseError::Http(ServerError::new(http_response)))
Err(FromHttpResponseError::Http(ServerError::Unknown(
crate::error::ResponseDeserializationError::from_response(http_response),
)))
}
}
}