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::{
|
use syn::{
|
||||||
braced,
|
braced,
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
Field, FieldValue, Ident, Token,
|
Field, FieldValue, Ident, Token, Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod attribute;
|
mod attribute;
|
||||||
@ -32,6 +32,8 @@ pub struct Api {
|
|||||||
request: Request,
|
request: Request,
|
||||||
/// The `response` section of the macro.
|
/// The `response` section of the macro.
|
||||||
response: Response,
|
response: Response,
|
||||||
|
/// The `error` section of the macro.
|
||||||
|
error: Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<RawApi> for Api {
|
impl TryFrom<RawApi> for Api {
|
||||||
@ -42,6 +44,9 @@ impl TryFrom<RawApi> for Api {
|
|||||||
metadata: raw_api.metadata.try_into()?,
|
metadata: raw_api.metadata.try_into()?,
|
||||||
request: raw_api.request.try_into()?,
|
request: raw_api.request.try_into()?,
|
||||||
response: raw_api.response.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();
|
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 response_doc = format!("Data in the response from the `{}` API endpoint.", name);
|
||||||
|
|
||||||
|
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 _;
|
||||||
@ -473,7 +480,7 @@ 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;
|
type Error = ruma_api::error::FromHttpResponseError<#error>;
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn try_from(
|
fn try_from(
|
||||||
@ -488,13 +495,17 @@ impl ToTokens for Api {
|
|||||||
#response_init_fields
|
#response_init_fields
|
||||||
})
|
})
|
||||||
} else {
|
} 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 {
|
impl ruma_api::Endpoint for Request {
|
||||||
type Response = Response;
|
type Response = Response;
|
||||||
|
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 {
|
||||||
@ -519,6 +530,7 @@ mod kw {
|
|||||||
custom_keyword!(metadata);
|
custom_keyword!(metadata);
|
||||||
custom_keyword!(request);
|
custom_keyword!(request);
|
||||||
custom_keyword!(response);
|
custom_keyword!(response);
|
||||||
|
custom_keyword!(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The entire `ruma_api!` macro structure directly as it appears in the source code..
|
/// The entire `ruma_api!` macro structure directly as it appears in the source code..
|
||||||
@ -529,11 +541,18 @@ pub struct RawApi {
|
|||||||
pub request: RawRequest,
|
pub request: RawRequest,
|
||||||
/// The `response` section of the macro.
|
/// The `response` section of the macro.
|
||||||
pub response: RawResponse,
|
pub response: RawResponse,
|
||||||
|
/// The `error` section of the macro.
|
||||||
|
pub error: Option<RawErrorType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for RawApi {
|
impl Parse for RawApi {
|
||||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
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};
|
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
|
/// An error when converting one of ruma's endpoint-specific request or response
|
||||||
/// types to the corresponding http type.
|
/// types to the corresponding http type.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -92,14 +104,14 @@ impl std::error::Error for RequestDeserializationError {}
|
|||||||
/// response types.
|
/// response types.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum FromHttpResponseError {
|
pub enum FromHttpResponseError<E> {
|
||||||
/// Deserialization failed
|
/// Deserialization failed
|
||||||
Deserialization(ResponseDeserializationError),
|
Deserialization(ResponseDeserializationError),
|
||||||
/// The server returned a non-success status
|
/// 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 {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
|
Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
|
||||||
@ -108,13 +120,13 @@ impl Display for FromHttpResponseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ServerError> for FromHttpResponseError {
|
impl<E> From<ServerError<E>> for FromHttpResponseError<E> {
|
||||||
fn from(err: ServerError) -> Self {
|
fn from(err: ServerError<E>) -> Self {
|
||||||
Self::Http(err)
|
Self::Http(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ResponseDeserializationError> for FromHttpResponseError {
|
impl<E> From<ResponseDeserializationError> for FromHttpResponseError<E> {
|
||||||
fn from(err: ResponseDeserializationError) -> Self {
|
fn from(err: ResponseDeserializationError) -> Self {
|
||||||
Self::Deserialization(err)
|
Self::Deserialization(err)
|
||||||
}
|
}
|
||||||
@ -123,7 +135,7 @@ impl From<ResponseDeserializationError> for FromHttpResponseError {
|
|||||||
/// An error that occurred when trying to deserialize a response.
|
/// An error that occurred when trying to deserialize a response.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ResponseDeserializationError {
|
pub struct ResponseDeserializationError {
|
||||||
inner: DeserializationError,
|
inner: Option<DeserializationError>,
|
||||||
http_response: http::Response<Vec<u8>>,
|
http_response: http::Response<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,13 +147,25 @@ impl ResponseDeserializationError {
|
|||||||
inner: impl Into<DeserializationError>,
|
inner: impl Into<DeserializationError>,
|
||||||
http_response: http::Response<Vec<u8>>,
|
http_response: http::Response<Vec<u8>>,
|
||||||
) -> Self {
|
) -> 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 {
|
impl Display for ResponseDeserializationError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
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)
|
/// An error was reported by the server (HTTP status code 4xx or 5xx)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ServerError {
|
pub enum ServerError<E> {
|
||||||
http_response: http::Response<Vec<u8>>,
|
/// 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 {
|
impl<E: Display> Display for ServerError<E> {
|
||||||
/// 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 {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
match self.http_response.status().canonical_reason() {
|
match self {
|
||||||
Some(reason) => {
|
ServerError::Known(e) => Display::fmt(e, f),
|
||||||
write!(f, "HTTP status {} {}", self.http_response.status().as_str(), reason)
|
ServerError::Unknown(res_err) => Display::fmt(res_err, f),
|
||||||
}
|
|
||||||
None => write!(f, "HTTP status {}", self.http_response.status().as_str()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for ServerError {}
|
impl<E: std::error::Error> std::error::Error for ServerError<E> {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum SerializationError {
|
enum SerializationError {
|
||||||
|
28
src/lib.rs
28
src/lib.rs
@ -232,17 +232,32 @@ pub trait Outgoing {
|
|||||||
type Incoming;
|
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.
|
/// 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: Outgoing + TryInto<http::Request<Vec<u8>>, Error = IntoHttpError>
|
||||||
where
|
where
|
||||||
<Self as Outgoing>::Incoming: TryFrom<http::Request<Vec<u8>>, Error = FromHttpRequestError>,
|
<Self as Outgoing>::Incoming: TryFrom<http::Request<Vec<u8>>, Error = FromHttpRequestError>,
|
||||||
<Self::Response as Outgoing>::Incoming:
|
<Self::Response as Outgoing>::Incoming: TryFrom<
|
||||||
TryFrom<http::Response<Vec<u8>>, Error = FromHttpResponseError>,
|
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: Outgoing + TryInto<http::Response<Vec<u8>>, Error = IntoHttpError>;
|
||||||
|
/// Error type returned when response from endpoint fails.
|
||||||
|
type ResponseError: EndpointError;
|
||||||
|
|
||||||
/// Metadata about the endpoint.
|
/// Metadata about the endpoint.
|
||||||
const METADATA: Metadata;
|
const METADATA: Metadata;
|
||||||
@ -284,7 +299,7 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::{
|
error::{
|
||||||
FromHttpRequestError, FromHttpResponseError, IntoHttpError,
|
FromHttpRequestError, FromHttpResponseError, IntoHttpError,
|
||||||
RequestDeserializationError, ServerError,
|
RequestDeserializationError, ServerError, Void,
|
||||||
},
|
},
|
||||||
Endpoint, Metadata, Outgoing,
|
Endpoint, Metadata, Outgoing,
|
||||||
};
|
};
|
||||||
@ -302,6 +317,7 @@ mod tests {
|
|||||||
|
|
||||||
impl Endpoint for Request {
|
impl Endpoint for Request {
|
||||||
type Response = Response;
|
type Response = Response;
|
||||||
|
type ResponseError = Void;
|
||||||
|
|
||||||
const METADATA: Metadata = Metadata {
|
const METADATA: Metadata = Metadata {
|
||||||
description: "Add an alias to a room.",
|
description: "Add an alias to a room.",
|
||||||
@ -384,13 +400,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<http::Response<Vec<u8>>> for Response {
|
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> {
|
fn try_from(http_response: http::Response<Vec<u8>>) -> Result<Response, Self::Error> {
|
||||||
if http_response.status().as_u16() < 400 {
|
if http_response.status().as_u16() < 400 {
|
||||||
Ok(Response)
|
Ok(Response)
|
||||||
} else {
|
} 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