Further split the error types
This commit is contained in:
parent
abb278c85a
commit
89f8d0f656
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
* Instead of one `Error` type, there is now two: `FromHttpError` and `IntoHttpError`
|
* Instead of one `Error` type, there is now many
|
||||||
* In constrast to the old `Error` types, these can be pattern matched and generally allow getting
|
* The new types live in their own `error` module
|
||||||
much more context for why things failed
|
* They provide access to details that were previously hidden
|
||||||
* Out Minimum Supported Rust Version is now 1.40.0
|
* Out Minimum Supported Rust Version is now 1.40.0
|
||||||
|
|
||||||
# 0.12.1
|
# 0.12.1
|
||||||
|
@ -154,12 +154,20 @@ impl ToTokens for Api {
|
|||||||
quote! {
|
quote! {
|
||||||
#path_var_ident: {
|
#path_var_ident: {
|
||||||
use std::ops::Deref as _;
|
use std::ops::Deref as _;
|
||||||
|
use ruma_api::error::RequestDeserializationError;
|
||||||
|
|
||||||
let segment = path_segments.get(#i).unwrap().as_bytes();
|
let segment = path_segments.get(#i).unwrap().as_bytes();
|
||||||
let decoded =
|
let decoded =
|
||||||
ruma_api::exports::percent_encoding::percent_decode(segment)
|
ruma_api::exports::percent_encoding::percent_decode(segment)
|
||||||
.decode_utf8_lossy();
|
.decode_utf8_lossy();
|
||||||
ruma_api::exports::serde_json::from_str(decoded.deref())?
|
match ruma_api::exports::serde_json::from_str(decoded.deref()) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(
|
||||||
|
RequestDeserializationError::new(err, request).into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -230,13 +238,31 @@ impl ToTokens for Api {
|
|||||||
|
|
||||||
let extract_request_query = if self.request.query_map_field().is_some() {
|
let extract_request_query = if self.request.query_map_field().is_some() {
|
||||||
quote! {
|
quote! {
|
||||||
let request_query =
|
let request_query = match ruma_api::exports::serde_urlencoded::from_str(
|
||||||
ruma_api::exports::serde_urlencoded::from_str(&request.uri().query().unwrap_or(""))?;
|
&request.uri().query().unwrap_or("")
|
||||||
|
) {
|
||||||
|
Ok(query) => query,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(
|
||||||
|
ruma_api::error::RequestDeserializationError::new(err, request).into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else if self.request.has_query_fields() {
|
} else if self.request.has_query_fields() {
|
||||||
quote! {
|
quote! {
|
||||||
let request_query: RequestQuery =
|
let request_query: RequestQuery =
|
||||||
ruma_api::exports::serde_urlencoded::from_str(&request.uri().query().unwrap_or(""))?;
|
match ruma_api::exports::serde_urlencoded::from_str(
|
||||||
|
&request.uri().query().unwrap_or("")
|
||||||
|
) {
|
||||||
|
Ok(query) => query,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(
|
||||||
|
ruma_api::error::RequestDeserializationError::new(err, request)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TokenStream::new()
|
TokenStream::new()
|
||||||
@ -274,7 +300,15 @@ impl ToTokens for Api {
|
|||||||
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 as ruma_api::Outgoing>::Incoming =
|
||||||
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,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(
|
||||||
|
ruma_api::error::RequestDeserializationError::new(err, request)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TokenStream::new()
|
TokenStream::new()
|
||||||
@ -325,21 +359,30 @@ impl ToTokens for Api {
|
|||||||
|
|
||||||
let extract_response_headers = if self.response.has_header_fields() {
|
let extract_response_headers = if self.response.has_header_fields() {
|
||||||
quote! {
|
quote! {
|
||||||
let mut headers = http_response.headers().clone();
|
let mut headers = response.headers().clone();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
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! {
|
{
|
||||||
let response_body: <ResponseBody as ruma_api::Outgoing>::Incoming =
|
quote! {
|
||||||
ruma_api::exports::serde_json::from_slice(response_body.as_slice())?;
|
let response_body: <ResponseBody as ruma_api::Outgoing>::Incoming =
|
||||||
}
|
match ruma_api::exports::serde_json::from_slice(response.body().as_slice()) {
|
||||||
} else {
|
Ok(body) => body,
|
||||||
TokenStream::new()
|
Err(err) => {
|
||||||
};
|
return Err(
|
||||||
|
ruma_api::error::ResponseDeserializationError::new(err, response)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TokenStream::new()
|
||||||
|
};
|
||||||
|
|
||||||
let response_init_fields = self.response.init_fields();
|
let response_init_fields = self.response.init_fields();
|
||||||
|
|
||||||
@ -365,7 +408,7 @@ impl ToTokens for Api {
|
|||||||
#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::FromHttpError;
|
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> {
|
||||||
@ -384,7 +427,7 @@ 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::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> {
|
||||||
@ -415,7 +458,7 @@ impl ToTokens for Api {
|
|||||||
#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::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> {
|
||||||
@ -429,23 +472,22 @@ 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::FromHttpError;
|
type Error = ruma_api::error::FromHttpResponseError;
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn try_from(
|
fn try_from(
|
||||||
http_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 http_response.status().is_success() {
|
if response.status().as_u16() < 400 {
|
||||||
#extract_response_headers
|
#extract_response_headers
|
||||||
|
|
||||||
let response_body = http_response.into_body();
|
|
||||||
#typed_response_body_decl
|
#typed_response_body_decl
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
#response_init_fields
|
#response_init_fields
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(ruma_api::FromHttpError::Http(http_response))
|
Err(ruma_api::error::ServerError::new(response).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,21 @@ impl Request {
|
|||||||
let header_name_string = header_name.to_string();
|
let header_name_string = header_name.to_string();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#field_name: 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()) {
|
||||||
.ok_or(ruma_api::exports::serde_json::Error::missing_field(#header_name_string))?
|
Some(header) => header.to_owned(),
|
||||||
.to_owned()
|
None => {
|
||||||
|
return Err(
|
||||||
|
ruma_api::error::RequestDeserializationError::new(
|
||||||
|
ruma_api::exports::serde_json::Error::missing_field(
|
||||||
|
#header_name_string
|
||||||
|
),
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ impl Response {
|
|||||||
}
|
}
|
||||||
ResponseField::NewtypeRawBody(_) => {
|
ResponseField::NewtypeRawBody(_) => {
|
||||||
quote_spanned! {span=>
|
quote_spanned! {span=>
|
||||||
#field_name: response_body
|
#field_name: response.into_body()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
219
src/error.rs
Normal file
219
src/error.rs
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
//! This module contains types for all kinds of errors that can occur when
|
||||||
|
//! converting between http requests / responses and ruma's representation of
|
||||||
|
//! matrix API requests / responses.
|
||||||
|
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
/// An error when converting one of ruma's endpoint-specific request or response
|
||||||
|
/// types to the corresponding http type.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IntoHttpError(SerializationError);
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl From<serde_json::Error> for IntoHttpError {
|
||||||
|
fn from(err: serde_json::Error) -> Self {
|
||||||
|
Self(SerializationError::Json(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl From<serde_urlencoded::ser::Error> for IntoHttpError {
|
||||||
|
fn from(err: serde_urlencoded::ser::Error) -> Self {
|
||||||
|
Self(SerializationError::Query(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IntoHttpError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match &self.0 {
|
||||||
|
SerializationError::Json(err) => write!(f, "JSON serialization failed: {}", err),
|
||||||
|
SerializationError::Query(err) => {
|
||||||
|
write!(f, "Query parameter serialization failed: {}", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for IntoHttpError {}
|
||||||
|
|
||||||
|
/// An error when converting a http request to one of ruma's endpoint-specific
|
||||||
|
/// request types.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum FromHttpRequestError {
|
||||||
|
/// Deserialization failed
|
||||||
|
Deserialization(RequestDeserializationError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FromHttpRequestError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RequestDeserializationError> for FromHttpRequestError {
|
||||||
|
fn from(err: RequestDeserializationError) -> Self {
|
||||||
|
Self::Deserialization(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for FromHttpRequestError {}
|
||||||
|
|
||||||
|
/// An error that occurred when trying to deserialize a request.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RequestDeserializationError {
|
||||||
|
inner: DeserializationError,
|
||||||
|
http_request: http::Request<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestDeserializationError {
|
||||||
|
/// 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(
|
||||||
|
inner: impl Into<DeserializationError>,
|
||||||
|
http_request: http::Request<Vec<u8>>,
|
||||||
|
) -> Self {
|
||||||
|
Self { inner: inner.into(), http_request }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RequestDeserializationError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
Display::fmt(&self.inner, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for RequestDeserializationError {}
|
||||||
|
|
||||||
|
/// An error when converting a http response to one of ruma's endpoint-specific
|
||||||
|
/// response types.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum FromHttpResponseError {
|
||||||
|
/// Deserialization failed
|
||||||
|
Deserialization(ResponseDeserializationError),
|
||||||
|
/// The server returned a non-success status
|
||||||
|
Http(ServerError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FromHttpResponseError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
|
||||||
|
Self::Http(err) => write!(f, "the server returned an error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ServerError> for FromHttpResponseError {
|
||||||
|
fn from(err: ServerError) -> Self {
|
||||||
|
Self::Http(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResponseDeserializationError> for FromHttpResponseError {
|
||||||
|
fn from(err: ResponseDeserializationError) -> Self {
|
||||||
|
Self::Deserialization(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error that occurred when trying to deserialize a response.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ResponseDeserializationError {
|
||||||
|
inner: DeserializationError,
|
||||||
|
http_response: http::Response<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseDeserializationError {
|
||||||
|
/// 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(
|
||||||
|
inner: impl Into<DeserializationError>,
|
||||||
|
http_response: http::Response<Vec<u8>>,
|
||||||
|
) -> Self {
|
||||||
|
Self { inner: inner.into(), http_response }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ResponseDeserializationError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
Display::fmt(&self.inner, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ServerError {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum SerializationError {
|
||||||
|
Json(serde_json::Error),
|
||||||
|
Query(serde_urlencoded::ser::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This type is public so it is accessible from `ruma_api!` generated code.
|
||||||
|
/// It is not considered part of ruma-api's public API.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DeserializationError {
|
||||||
|
Json(serde_json::Error),
|
||||||
|
Query(serde_urlencoded::de::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DeserializationError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
DeserializationError::Json(err) => Display::fmt(err, f),
|
||||||
|
DeserializationError::Query(err) => Display::fmt(err, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl From<serde_json::Error> for DeserializationError {
|
||||||
|
fn from(err: serde_json::Error) -> Self {
|
||||||
|
Self::Json(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl From<serde_urlencoded::de::Error> for DeserializationError {
|
||||||
|
fn from(err: serde_urlencoded::de::Error) -> Self {
|
||||||
|
Self::Query(err)
|
||||||
|
}
|
||||||
|
}
|
116
src/lib.rs
116
src/lib.rs
@ -13,11 +13,7 @@
|
|||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
#![deny(missing_copy_implementations, missing_debug_implementations, missing_docs)]
|
#![deny(missing_copy_implementations, missing_debug_implementations, missing_docs)]
|
||||||
|
|
||||||
use std::{
|
use std::convert::{TryFrom, TryInto};
|
||||||
convert::{TryFrom, TryInto},
|
|
||||||
error::Error as StdError,
|
|
||||||
fmt::{Display, Formatter, Result as FmtResult},
|
|
||||||
};
|
|
||||||
|
|
||||||
use http::Method;
|
use http::Method;
|
||||||
|
|
||||||
@ -208,6 +204,7 @@ pub use ruma_api_macros::ruma_api;
|
|||||||
#[cfg(feature = "with-ruma-api-macros")]
|
#[cfg(feature = "with-ruma-api-macros")]
|
||||||
pub use ruma_api_macros::Outgoing;
|
pub use ruma_api_macros::Outgoing;
|
||||||
|
|
||||||
|
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.
|
||||||
#[cfg(feature = "with-ruma-api-macros")]
|
#[cfg(feature = "with-ruma-api-macros")]
|
||||||
@ -221,6 +218,8 @@ pub mod exports {
|
|||||||
pub use url;
|
pub use url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError};
|
||||||
|
|
||||||
/// A type that can be sent to another party that understands the matrix protocol. If any of the
|
/// 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
|
/// 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
|
/// corresponding 'Incoming' type that supports deserialization. This is useful for things like
|
||||||
@ -238,8 +237,9 @@ pub trait Outgoing {
|
|||||||
/// 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 = FromHttpError>,
|
<Self as Outgoing>::Incoming: TryFrom<http::Request<Vec<u8>>, Error = FromHttpRequestError>,
|
||||||
<Self::Response as Outgoing>::Incoming: TryFrom<http::Response<Vec<u8>>, Error = FromHttpError>,
|
<Self::Response as Outgoing>::Incoming:
|
||||||
|
TryFrom<http::Response<Vec<u8>>, Error = FromHttpResponseError>,
|
||||||
{
|
{
|
||||||
/// 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>;
|
||||||
@ -248,78 +248,6 @@ where
|
|||||||
const METADATA: Metadata;
|
const METADATA: Metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error when converting an `http` request or response to the corresponding
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum FromHttpError {
|
|
||||||
/// The server returned a non-success status
|
|
||||||
Http(http::Response<Vec<u8>>),
|
|
||||||
/// JSON deserialization failed
|
|
||||||
Json(serde_json::Error),
|
|
||||||
/// Deserialization of query parameters failed
|
|
||||||
Query(serde_urlencoded::de::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error when converting a request or response to the corresponding http type.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum IntoHttpError {
|
|
||||||
/// JSON serialization failed
|
|
||||||
Json(serde_json::Error),
|
|
||||||
/// Serialization of query parameters failed
|
|
||||||
Query(serde_urlencoded::ser::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<serde_json::Error> for FromHttpError {
|
|
||||||
fn from(err: serde_json::Error) -> Self {
|
|
||||||
Self::Json(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<serde_urlencoded::de::Error> for FromHttpError {
|
|
||||||
fn from(err: serde_urlencoded::de::Error) -> Self {
|
|
||||||
Self::Query(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for FromHttpError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
match self {
|
|
||||||
Self::Http(res) => match res.status().canonical_reason() {
|
|
||||||
Some(reason) => write!(f, "HTTP status {} {}", res.status().as_str(), reason),
|
|
||||||
None => write!(f, "HTTP status {}", res.status().as_str()),
|
|
||||||
},
|
|
||||||
Self::Json(err) => write!(f, "JSON deserialization failed: {}", err),
|
|
||||||
Self::Query(err) => write!(f, "Query parameter deserialization failed: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdError for FromHttpError {}
|
|
||||||
|
|
||||||
impl From<serde_json::Error> for IntoHttpError {
|
|
||||||
fn from(err: serde_json::Error) -> Self {
|
|
||||||
Self::Json(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<serde_urlencoded::ser::Error> for IntoHttpError {
|
|
||||||
fn from(err: serde_urlencoded::ser::Error) -> Self {
|
|
||||||
Self::Query(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for IntoHttpError {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
|
||||||
match self {
|
|
||||||
Self::Json(err) => write!(f, "JSON serialization failed: {}", err),
|
|
||||||
Self::Query(err) => write!(f, "Query parameter serialization failed: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdError for IntoHttpError {}
|
|
||||||
|
|
||||||
/// Metadata about an API endpoint.
|
/// Metadata about an API endpoint.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
@ -353,7 +281,13 @@ mod tests {
|
|||||||
use ruma_identifiers::{RoomAliasId, RoomId};
|
use ruma_identifiers::{RoomAliasId, RoomId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{Endpoint, FromHttpError, IntoHttpError, Metadata, Outgoing};
|
use crate::{
|
||||||
|
error::{
|
||||||
|
FromHttpRequestError, FromHttpResponseError, IntoHttpError,
|
||||||
|
RequestDeserializationError, ServerError,
|
||||||
|
},
|
||||||
|
Endpoint, Metadata, Outgoing,
|
||||||
|
};
|
||||||
|
|
||||||
/// A request to create a new room alias.
|
/// A request to create a new room alias.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -403,18 +337,28 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<http::Request<Vec<u8>>> for Request {
|
impl TryFrom<http::Request<Vec<u8>>> for Request {
|
||||||
type Error = FromHttpError;
|
type Error = FromHttpRequestError;
|
||||||
|
|
||||||
fn try_from(request: http::Request<Vec<u8>>) -> Result<Self, Self::Error> {
|
fn try_from(request: http::Request<Vec<u8>>) -> Result<Self, Self::Error> {
|
||||||
let request_body: RequestBody =
|
let request_body: RequestBody =
|
||||||
::serde_json::from_slice(request.body().as_slice())?;
|
match serde_json::from_slice(request.body().as_slice()) {
|
||||||
|
Ok(body) => body,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(RequestDeserializationError::new(err, request).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect();
|
let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect();
|
||||||
Ok(Request {
|
Ok(Request {
|
||||||
room_id: request_body.room_id,
|
room_id: request_body.room_id,
|
||||||
room_alias: {
|
room_alias: {
|
||||||
let segment = path_segments.get(5).unwrap().as_bytes();
|
let segment = path_segments.get(5).unwrap().as_bytes();
|
||||||
let decoded = percent_encoding::percent_decode(segment).decode_utf8_lossy();
|
let decoded = percent_encoding::percent_decode(segment).decode_utf8_lossy();
|
||||||
serde_json::from_str(decoded.deref())?
|
match serde_json::from_str(decoded.deref()) {
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(RequestDeserializationError::new(err, request).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -434,13 +378,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<http::Response<Vec<u8>>> for Response {
|
impl TryFrom<http::Response<Vec<u8>>> for Response {
|
||||||
type Error = FromHttpError;
|
type Error = FromHttpResponseError;
|
||||||
|
|
||||||
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().is_success() {
|
if http_response.status().as_u16() < 400 {
|
||||||
Ok(Response)
|
Ok(Response)
|
||||||
} else {
|
} else {
|
||||||
Err(FromHttpError::Http(http_response))
|
Err(FromHttpResponseError::Http(ServerError::new(http_response)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user