api: Introduce IncomingResponse trait

This commit is contained in:
Jonas Platte 2021-04-09 15:29:39 +02:00
parent effb53444d
commit 2ac020173b
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
10 changed files with 94 additions and 73 deletions

View File

@ -28,6 +28,7 @@ impl Response {
/// Produces code for a response struct initializer.
fn init_fields(&self, ruma_api: &TokenStream) -> TokenStream {
let bytes = quote! { #ruma_api::exports::bytes };
let http = quote! { #ruma_api::exports::http };
let mut fields = vec![];
@ -79,7 +80,13 @@ impl Response {
// We are guaranteed only one new body field because of a check in `try_from`.
ResponseField::NewtypeRawBody(_) => {
new_type_raw_body = Some(quote_spanned! {span=>
#field_name: response.into_body()
#field_name: {
let mut reader = #bytes::Buf::reader(response.into_body());
let mut vec = ::std::vec::Vec::new();
::std::io::Read::read_to_end(&mut reader, &mut vec)
.expect("reading from a bytes::Buf never fails");
vec
}
});
// skip adding to the vec
continue;
@ -194,6 +201,7 @@ impl Response {
error_ty: &TokenStream,
ruma_api: &TokenStream,
) -> TokenStream {
let bytes = quote! { #ruma_api::exports::bytes };
let http = quote! { #ruma_api::exports::http };
let ruma_serde = quote! { #ruma_api::exports::ruma_serde };
let serde = quote! { #ruma_api::exports::serde };
@ -218,15 +226,15 @@ impl Response {
ResponseBody
as #ruma_serde::Outgoing
>::Incoming = {
// If the reponse body is completely empty, pretend it is an empty JSON object
// instead. This allows reponses with only optional body parameters to be
// deserialized in that case.
let json = match response.body().as_slice() {
b"" => b"{}",
body => body,
};
#serde_json::from_slice(json)?
let body = response.into_body();
if #bytes::Buf::has_remaining(&body) {
#serde_json::from_reader(#bytes::Buf::reader(body))?
} else {
// If the reponse body is completely empty, pretend it is an empty JSON
// object instead. This allows reponses with only optional body
// parameters to be deserialized in that case.
#serde_json::from_str("{}")?
}
};
}
} else {
@ -299,12 +307,15 @@ impl Response {
#[automatically_derived]
#[cfg(feature = "client")]
impl ::std::convert::TryFrom<#http::Response<Vec<u8>>> for Response {
type Error = #ruma_api::error::FromHttpResponseError<#error_ty>;
impl #ruma_api::IncomingResponse for Response {
type EndpointError = #error_ty;
fn try_from(
response: #http::Response<Vec<u8>>,
) -> ::std::result::Result<Self, Self::Error> {
fn try_from_http_response<T: #bytes::Buf>(
response: #http::Response<T>,
) -> ::std::result::Result<
Self,
#ruma_api::error::FromHttpResponseError<#error_ty>,
> {
if response.status().as_u16() < 400 {
#extract_response_headers

View File

@ -19,6 +19,7 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
bytes = "1.0.1"
http = "0.2.2"
percent-encoding = "2.1.0"
ruma-api-macros = { version = "=0.17.0-alpha.2", path = "../ruma-api-macros" }

View File

@ -4,6 +4,7 @@
use std::{error::Error as StdError, fmt};
use bytes::Buf;
use thiserror::Error;
use crate::EndpointError;
@ -14,8 +15,8 @@ use crate::EndpointError;
pub enum Void {}
impl EndpointError for Void {
fn try_from_response(
_response: http::Response<Vec<u8>>,
fn try_from_response<T: Buf>(
_response: http::Response<T>,
) -> Result<Self, ResponseDeserializationError> {
Err(ResponseDeserializationError::none())
}
@ -121,18 +122,12 @@ impl<E> From<ServerError<E>> for FromHttpResponseError<E> {
}
}
impl<E> From<ResponseDeserializationError> for FromHttpResponseError<E> {
fn from(err: ResponseDeserializationError) -> Self {
Self::Deserialization(err)
}
}
impl<E, T> From<T> for FromHttpResponseError<E>
where
T: Into<DeserializationError>,
T: Into<ResponseDeserializationError>,
{
fn from(err: T) -> Self {
Self::Deserialization(ResponseDeserializationError::new(err))
Self::Deserialization(err.into())
}
}
@ -145,17 +140,20 @@ pub struct ResponseDeserializationError {
}
impl ResponseDeserializationError {
/// Creates a new `ResponseDeserializationError` from the given deserialization error and http
/// response.
pub fn new(inner: impl Into<DeserializationError>) -> Self {
Self { inner: Some(inner.into()) }
}
fn none() -> Self {
Self { inner: None }
}
}
impl<T> From<T> for ResponseDeserializationError
where
T: Into<DeserializationError>,
{
fn from(err: T) -> Self {
Self { inner: Some(err.into()) }
}
}
impl fmt::Display for ResponseDeserializationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref inner) = self.inner {

View File

@ -20,12 +20,10 @@
#[cfg(not(all(feature = "client", feature = "server")))]
compile_error!("ruma_api's Cargo features only exist as a workaround are not meant to be disabled");
use std::{
convert::{TryFrom, TryInto},
error::Error as StdError,
};
use std::{convert::TryInto, error::Error as StdError};
use http::{uri::PathAndQuery, Method};
use bytes::Buf;
use http::Method;
use ruma_identifiers::UserId;
/// Generates a `ruma_api::Endpoint` from a concise definition.
@ -206,6 +204,7 @@ pub mod error;
/// It is not considered part of ruma-api's public API.
#[doc(hidden)]
pub mod exports {
pub use bytes;
pub use http;
pub use percent_encoding;
pub use ruma_serde;
@ -221,8 +220,8 @@ pub trait EndpointError: StdError + Sized + 'static {
///
/// 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>>,
fn try_from_response<T: Buf>(
response: http::Response<T>,
) -> Result<Self, error::ResponseDeserializationError>;
}
@ -232,10 +231,7 @@ pub trait OutgoingRequest: Sized {
type EndpointError: EndpointError;
/// Response type returned when the request is successful.
type IncomingResponse: TryFrom<
http::Response<Vec<u8>>,
Error = FromHttpResponseError<Self::EndpointError>,
>;
type IncomingResponse: IncomingResponse<EndpointError = Self::EndpointError>;
/// Metadata about the endpoint.
const METADATA: Metadata;
@ -255,6 +251,17 @@ pub trait OutgoingRequest: Sized {
) -> Result<http::Request<Vec<u8>>, IntoHttpError>;
}
/// A response type for a Matrix API endpoint, used for receiving responses.
pub trait IncomingResponse: Sized {
/// A type capturing the expected error conditions the server can return.
type EndpointError: EndpointError;
/// Tries to convert the given `http::Response` into this response type.
fn try_from_http_response<T: Buf>(
response: http::Response<T>,
) -> Result<Self, FromHttpResponseError<Self::EndpointError>>;
}
/// An extension to `OutgoingRequest` which provides Appservice specific methods
pub trait OutgoingRequestAppserviceExt: OutgoingRequest {
/// Tries to convert this request into an `http::Request` and appends a virtual `user_id` to
@ -283,7 +290,7 @@ pub trait OutgoingRequestAppserviceExt: OutgoingRequest {
};
parts.path_and_query =
Some(PathAndQuery::try_from(path_and_query_with_user_id).map_err(http::Error::from)?);
Some(path_and_query_with_user_id.try_into().map_err(http::Error::from)?);
*http_request.uri_mut() = parts.try_into().map_err(http::Error::from)?;

View File

@ -2,10 +2,12 @@
use std::convert::TryFrom;
use bytes::Buf;
use http::{header::CONTENT_TYPE, method::Method};
use ruma_api::{
error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError, ServerError, Void},
try_deserialize, AuthScheme, EndpointError, IncomingRequest, Metadata, OutgoingRequest,
try_deserialize, AuthScheme, EndpointError, IncomingRequest, IncomingResponse, Metadata,
OutgoingRequest,
};
use ruma_identifiers::{RoomAliasId, RoomId};
use ruma_serde::Outgoing;
@ -99,10 +101,12 @@ impl Outgoing for Response {
type Incoming = Self;
}
impl TryFrom<http::Response<Vec<u8>>> for Response {
type Error = FromHttpResponseError<Void>;
impl IncomingResponse for Response {
type EndpointError = Void;
fn try_from(http_response: http::Response<Vec<u8>>) -> Result<Response, Self::Error> {
fn try_from_http_response<T: Buf>(
http_response: http::Response<T>,
) -> Result<Self, FromHttpResponseError<Void>> {
if http_response.status().as_u16() < 400 {
Ok(Response)
} else {

View File

@ -1,8 +1,9 @@
use std::convert::TryFrom;
use bytes::Buf;
use ruma_api::{
error::{FromHttpResponseError, IntoHttpError, Void},
ruma_api,
ruma_api, IncomingResponse,
};
use ruma_serde::Outgoing;
@ -26,10 +27,12 @@ ruma_api! {
#[derive(Outgoing)]
pub struct Response;
impl TryFrom<http::Response<Vec<u8>>> for Response {
type Error = FromHttpResponseError<Void>;
impl IncomingResponse for Response {
type EndpointError = Void;
fn try_from(_: http::Response<Vec<u8>>) -> Result<Self, Self::Error> {
fn try_from_http_response<T: Buf>(
_: http::Response<T>,
) -> Result<Self, FromHttpResponseError<Void>> {
todo!()
}
}

View File

@ -17,6 +17,7 @@ edition = "2018"
[dependencies]
assign = "1.1.1"
bytes = "1.0.1"
http = "0.2.2"
js_int = { version = "0.2.0", features = ["serde"] }
maplit = "1.0.2"

View File

@ -2,10 +2,11 @@
use std::{collections::BTreeMap, fmt, time::Duration};
use bytes::Buf;
use ruma_api::{error::ResponseDeserializationError, EndpointError};
use ruma_identifiers::RoomVersionId;
use serde::{Deserialize, Serialize};
use serde_json::{from_slice as from_json_slice, to_vec as to_json_vec, Value as JsonValue};
use serde_json::{from_reader as from_json_reader, to_vec as to_json_vec, Value as JsonValue};
/// Deserialize and Serialize implementations for ErrorKind.
/// Separate module because it's a lot of code.
@ -202,13 +203,12 @@ pub struct Error {
}
impl EndpointError for Error {
fn try_from_response(
response: http::Response<Vec<u8>>,
fn try_from_response<T: Buf>(
response: http::Response<T>,
) -> Result<Self, ResponseDeserializationError> {
match from_json_slice::<ErrorBody>(response.body()) {
Ok(error_body) => Ok(error_body.into_error(response.status())),
Err(de_error) => Err(ResponseDeserializationError::new(de_error)),
}
let status = response.status();
let error_body: ErrorBody = from_json_reader(response.into_body().reader())?;
Ok(error_body.into_error(status))
}
}

View File

@ -2,11 +2,12 @@
use std::{collections::BTreeMap, fmt};
use bytes::Buf;
use ruma_api::{error::ResponseDeserializationError, EndpointError};
use ruma_serde::Outgoing;
use serde::{Deserialize, Serialize};
use serde_json::{
from_slice as from_json_slice, to_vec as to_json_vec, value::RawValue as RawJsonValue,
from_reader as from_json_reader, to_vec as to_json_vec, value::RawValue as RawJsonValue,
Value as JsonValue,
};
@ -133,16 +134,14 @@ impl From<MatrixError> for UiaaResponse {
}
impl EndpointError for UiaaResponse {
fn try_from_response(
response: http::Response<Vec<u8>>,
fn try_from_response<T: Buf>(
response: http::Response<T>,
) -> Result<Self, ResponseDeserializationError> {
if response.status() == http::StatusCode::UNAUTHORIZED {
if let Ok(authentication_info) = from_json_slice::<UiaaInfo>(response.body()) {
return Ok(UiaaResponse::AuthResponse(authentication_info));
}
Ok(UiaaResponse::AuthResponse(from_json_reader(response.into_body().reader())?))
} else {
MatrixError::try_from_response(response).map(From::from)
}
MatrixError::try_from_response(response).map(From::from)
}
}
@ -383,7 +382,7 @@ mod tests {
let http_response = http::Response::builder()
.status(http::StatusCode::UNAUTHORIZED)
.body(json.into())
.body(json.as_bytes())
.unwrap();
let parsed_uiaa_info = match UiaaResponse::try_from_response(http_response).unwrap() {

View File

@ -104,7 +104,6 @@
use std::{
collections::BTreeMap,
convert::TryFrom,
sync::{Arc, Mutex},
time::Duration,
};
@ -374,11 +373,9 @@ impl Client {
let hyper_response = client.hyper.request(http_request.map(hyper::Body::from)).await?;
let (head, body) = hyper_response.into_parts();
// FIXME: We read the response into a contiguous buffer here (not actually required for
// deserialization) and then copy the whole thing to convert from Bytes to Vec<u8>.
let full_body = hyper::body::to_bytes(body).await?;
let full_response = HttpResponse::from_parts(head, full_body.as_ref().to_owned());
let full_body = hyper::body::aggregate(body).await?;
let full_response = HttpResponse::from_parts(head, full_body);
Ok(Request::IncomingResponse::try_from(full_response)?)
Ok(ruma_api::IncomingResponse::try_from_http_response(full_response)?)
}
}