Enable deserialization of unsuccessful responses
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
This commit is contained in:
parent
6f5e25cb7d
commit
6a0ab987b5
@ -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 })
|
||||
}
|
||||
}
|
||||
|
76
src/error.rs
76
src/error.rs
@ -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 {
|
||||
|
28
src/lib.rs
28
src/lib.rs
@ -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),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user