Make conversions from Ruma types to http types generic

So users can select the Body type that makes the most sense for them.
This commit is contained in:
Jonas Platte 2021-04-23 12:22:36 +02:00
parent ae26be88c5
commit f818b53ca1
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
23 changed files with 107 additions and 64 deletions

View File

@ -11,10 +11,10 @@ impl Request {
lifetimes: &TokenStream, lifetimes: &TokenStream,
ruma_api: &TokenStream, ruma_api: &TokenStream,
) -> TokenStream { ) -> TokenStream {
let bytes = quote! { #ruma_api::exports::bytes };
let http = quote! { #ruma_api::exports::http }; let http = quote! { #ruma_api::exports::http };
let percent_encoding = quote! { #ruma_api::exports::percent_encoding }; let percent_encoding = quote! { #ruma_api::exports::percent_encoding };
let ruma_serde = quote! { #ruma_api::exports::ruma_serde }; let ruma_serde = quote! { #ruma_api::exports::ruma_serde };
let serde_json = quote! { #ruma_api::exports::serde_json };
let method = &metadata.method; let method = &metadata.method;
let request_path_string = if self.has_path_fields() { let request_path_string = if self.has_path_fields() {
@ -165,7 +165,7 @@ impl Request {
let request_body = if let Some(field) = self.newtype_raw_body_field() { let request_body = if let Some(field) = self.newtype_raw_body_field() {
let field_name = field.ident.as_ref().expect("expected field to have an identifier"); let field_name = field.ident.as_ref().expect("expected field to have an identifier");
quote! { self.#field_name } quote! { #ruma_serde::slice_to_buf(&self.#field_name) }
} else if self.has_body_fields() || self.newtype_body_field().is_some() { } else if self.has_body_fields() || self.newtype_body_field().is_some() {
let request_body_initializers = if let Some(field) = self.newtype_body_field() { let request_body_initializers = if let Some(field) = self.newtype_body_field() {
let field_name = let field_name =
@ -177,13 +177,10 @@ impl Request {
}; };
quote! { quote! {
{ #ruma_serde::json_to_buf(&RequestBody #request_body_initializers)?
let request_body = RequestBody #request_body_initializers;
#serde_json::to_vec(&request_body)?
}
} }
} else { } else {
quote! { Vec::new() } quote! { <T as ::std::default::Default>::default() }
}; };
let non_auth_impls = metadata.authentication.iter().map(|auth| { let non_auth_impls = metadata.authentication.iter().map(|auth| {
@ -210,14 +207,11 @@ impl Request {
const METADATA: #ruma_api::Metadata = self::METADATA; const METADATA: #ruma_api::Metadata = self::METADATA;
fn try_into_http_request( fn try_into_http_request<T: ::std::default::Default + #bytes::BufMut>(
self, self,
base_url: &::std::primitive::str, base_url: &::std::primitive::str,
access_token: #ruma_api::SendAccessToken<'_>, access_token: #ruma_api::SendAccessToken<'_>,
) -> ::std::result::Result< ) -> ::std::result::Result<#http::Request<T>, #ruma_api::error::IntoHttpError> {
#http::Request<::std::vec::Vec<::std::primitive::u8>>,
#ruma_api::error::IntoHttpError,
> {
let metadata = self::METADATA; let metadata = self::METADATA;
let mut req_builder = #http::Request::builder() let mut req_builder = #http::Request::builder()

View File

@ -5,8 +5,9 @@ use super::{Response, ResponseField};
impl Response { impl Response {
pub fn expand_outgoing(&self, ruma_api: &TokenStream) -> TokenStream { pub fn expand_outgoing(&self, ruma_api: &TokenStream) -> TokenStream {
let bytes = quote! { #ruma_api::exports::bytes };
let http = quote! { #ruma_api::exports::http }; let http = quote! { #ruma_api::exports::http };
let serde_json = quote! { #ruma_api::exports::serde_json }; let ruma_serde = quote! { #ruma_api::exports::ruma_serde };
let serialize_response_headers = self.fields.iter().map(|response_field| { let serialize_response_headers = self.fields.iter().map(|response_field| {
if let ResponseField::Header(field, header_name) = response_field { if let ResponseField::Header(field, header_name) = response_field {
@ -40,11 +41,11 @@ impl Response {
let body = if let Some(field) = self.newtype_raw_body_field() { let body = if let Some(field) = self.newtype_raw_body_field() {
let field_name = field.ident.as_ref().expect("expected field to have an identifier"); let field_name = field.ident.as_ref().expect("expected field to have an identifier");
quote! { self.#field_name } quote! { #ruma_serde::slice_to_buf(&self.#field_name) }
} else if let Some(field) = self.newtype_body_field() { } else if let Some(field) = self.newtype_body_field() {
let field_name = field.ident.as_ref().expect("expected field to have an identifier"); let field_name = field.ident.as_ref().expect("expected field to have an identifier");
quote! { quote! {
#serde_json::to_vec(&self.#field_name)? #ruma_serde::json_to_buf(&self.#field_name)?
} }
} else { } else {
let fields = self.fields.iter().map(|response_field| { let fields = self.fields.iter().map(|response_field| {
@ -63,7 +64,7 @@ impl Response {
}); });
quote! { quote! {
#serde_json::to_vec(&ResponseBody { #(#fields)* })? #ruma_serde::json_to_buf(&ResponseBody { #(#fields)* })?
} }
}; };
@ -72,12 +73,9 @@ impl Response {
#[cfg(feature = "server")] #[cfg(feature = "server")]
#[allow(clippy::unknown_clippy_lints, clippy::inconsistent_struct_constructor)] #[allow(clippy::unknown_clippy_lints, clippy::inconsistent_struct_constructor)]
impl #ruma_api::OutgoingResponse for Response { impl #ruma_api::OutgoingResponse for Response {
fn try_into_http_response( fn try_into_http_response<T: ::std::default::Default + #bytes::BufMut>(
self, self,
) -> ::std::result::Result< ) -> ::std::result::Result<#http::Response<T>, #ruma_api::error::IntoHttpError> {
#http::Response<::std::vec::Vec<::std::primitive::u8>>,
#ruma_api::error::IntoHttpError,
> {
let mut resp_builder = #http::Response::builder() let mut resp_builder = #http::Response::builder()
.header(#http::header::CONTENT_TYPE, "application/json"); .header(#http::header::CONTENT_TYPE, "application/json");

View File

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

View File

@ -4,6 +4,7 @@
use std::{error::Error as StdError, fmt}; use std::{error::Error as StdError, fmt};
use bytes::BufMut;
use thiserror::Error; use thiserror::Error;
use crate::{EndpointError, OutgoingResponse}; use crate::{EndpointError, OutgoingResponse};
@ -14,7 +15,9 @@ use crate::{EndpointError, OutgoingResponse};
pub enum Void {} pub enum Void {}
impl OutgoingResponse for Void { impl OutgoingResponse for Void {
fn try_into_http_response(self) -> Result<http::Response<Vec<u8>>, IntoHttpError> { fn try_into_http_response<T: Default + BufMut>(
self,
) -> Result<http::Response<T>, IntoHttpError> {
match self {} match self {}
} }
} }

View File

@ -22,6 +22,7 @@ compile_error!("ruma_api's Cargo features only exist as a workaround are not mea
use std::{convert::TryInto as _, error::Error as StdError}; use std::{convert::TryInto as _, error::Error as StdError};
use bytes::BufMut;
use http::Method; use http::Method;
use ruma_identifiers::UserId; use ruma_identifiers::UserId;
@ -203,6 +204,7 @@ pub mod error;
/// It is not considered part of ruma-api's public API. /// It is not considered part of ruma-api's public API.
#[doc(hidden)] #[doc(hidden)]
pub mod exports { pub mod exports {
pub use bytes;
pub use http; pub use http;
pub use percent_encoding; pub use percent_encoding;
pub use ruma_serde; pub use ruma_serde;
@ -267,11 +269,11 @@ pub trait OutgoingRequest: Sized {
/// The endpoints path will be appended to the given `base_url`, for example /// The endpoints path will be appended to the given `base_url`, for example
/// `https://matrix.org`. Since all paths begin with a slash, it is not necessary for the /// `https://matrix.org`. Since all paths begin with a slash, it is not necessary for the
/// `base_url` to have a trailing slash. If it has one however, it will be ignored. /// `base_url` to have a trailing slash. If it has one however, it will be ignored.
fn try_into_http_request( fn try_into_http_request<T: Default + BufMut>(
self, self,
base_url: &str, base_url: &str,
access_token: SendAccessToken<'_>, access_token: SendAccessToken<'_>,
) -> Result<http::Request<Vec<u8>>, IntoHttpError>; ) -> Result<http::Request<T>, IntoHttpError>;
} }
/// A response type for a Matrix API endpoint, used for receiving responses. /// A response type for a Matrix API endpoint, used for receiving responses.
@ -291,12 +293,12 @@ pub trait OutgoingRequestAppserviceExt: OutgoingRequest {
/// [assert Appservice identity][id_assert]. /// [assert Appservice identity][id_assert].
/// ///
/// [id_assert]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion /// [id_assert]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion
fn try_into_http_request_with_user_id( fn try_into_http_request_with_user_id<T: Default + BufMut>(
self, self,
base_url: &str, base_url: &str,
access_token: SendAccessToken<'_>, access_token: SendAccessToken<'_>,
user_id: UserId, user_id: UserId,
) -> Result<http::Request<Vec<u8>>, IntoHttpError> { ) -> Result<http::Request<T>, IntoHttpError> {
let mut http_request = self.try_into_http_request(base_url, access_token)?; let mut http_request = self.try_into_http_request(base_url, access_token)?;
let user_id_query = let user_id_query =
ruma_serde::urlencoded::to_string(&[("user_id", &user_id.into_string())])?; ruma_serde::urlencoded::to_string(&[("user_id", &user_id.into_string())])?;
@ -346,7 +348,9 @@ pub trait OutgoingResponse {
/// ///
/// This method should only fail when when invalid header values are specified. It may also /// This method should only fail when when invalid header values are specified. It may also
/// fail with a serialization error in case of bugs in Ruma though. /// fail with a serialization error in case of bugs in Ruma though.
fn try_into_http_response(self) -> Result<http::Response<Vec<u8>>, IntoHttpError>; fn try_into_http_response<T: Default + BufMut>(
self,
) -> Result<http::Response<T>, IntoHttpError>;
} }
/// Gives users the ability to define their own serializable / deserializable errors. /// Gives users the ability to define their own serializable / deserializable errors.

View File

@ -48,8 +48,10 @@ fn request_serde() {
user: user_id!("@bazme:ruma.io"), user: user_id!("@bazme:ruma.io"),
}; };
let http_req = let http_req = req
req.clone().try_into_http_request("https://homeserver.tld", SendAccessToken::None).unwrap(); .clone()
.try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None)
.unwrap();
let req2 = Request::try_from_http_request(http_req).unwrap(); let req2 = Request::try_from_http_request(http_req).unwrap();
assert_eq!(req.hello, req2.hello); assert_eq!(req.hello, req2.hello);
@ -73,7 +75,7 @@ fn request_with_user_id_serde() {
let user_id = user_id!("@_virtual_:ruma.io"); let user_id = user_id!("@_virtual_:ruma.io");
let http_req = req let http_req = req
.try_into_http_request_with_user_id( .try_into_http_request_with_user_id::<Vec<u8>>(
"https://homeserver.tld", "https://homeserver.tld",
SendAccessToken::None, SendAccessToken::None,
user_id, user_id,
@ -131,7 +133,7 @@ mod without_query {
let user_id = user_id!("@_virtual_:ruma.io"); let user_id = user_id!("@_virtual_:ruma.io");
let http_req = req let http_req = req
.try_into_http_request_with_user_id( .try_into_http_request_with_user_id::<Vec<u8>>(
"https://homeserver.tld", "https://homeserver.tld",
SendAccessToken::None, SendAccessToken::None,
user_id, user_id,

View File

@ -28,7 +28,7 @@ ruma_api! {
#[test] #[test]
fn response_content_type_override() { fn response_content_type_override() {
let res = Response { stuff: "magic".into() }; let res = Response { stuff: "magic".into() };
let mut http_res = res.try_into_http_response().unwrap(); let mut http_res = res.try_into_http_response::<Vec<u8>>().unwrap();
// Test that we correctly replaced the default content type, // Test that we correctly replaced the default content type,
// not adding another content-type header. // not adding another content-type header.
@ -45,8 +45,9 @@ fn response_content_type_override() {
#[test] #[test]
fn request_content_type_override() { fn request_content_type_override() {
let req = Request { location: None, stuff: "magic".into() }; let req = Request { location: None, stuff: "magic".into() };
let mut http_req = let mut http_req = req
req.try_into_http_request("https://homeserver.tld", SendAccessToken::None).unwrap(); .try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None)
.unwrap();
assert_eq!( assert_eq!(
match http_req.headers_mut().entry(CONTENT_TYPE) { match http_req.headers_mut().entry(CONTENT_TYPE) {

View File

@ -2,6 +2,7 @@
use std::convert::TryFrom; use std::convert::TryFrom;
use bytes::BufMut;
use http::{header::CONTENT_TYPE, method::Method}; use http::{header::CONTENT_TYPE, method::Method};
use ruma_api::{ use ruma_api::{
error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError, ServerError, Void}, error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError, ServerError, Void},
@ -38,11 +39,11 @@ impl OutgoingRequest for Request {
const METADATA: Metadata = METADATA; const METADATA: Metadata = METADATA;
fn try_into_http_request( fn try_into_http_request<T: Default + BufMut>(
self, self,
base_url: &str, base_url: &str,
_access_token: SendAccessToken<'_>, _access_token: SendAccessToken<'_>,
) -> Result<http::Request<Vec<u8>>, IntoHttpError> { ) -> Result<http::Request<T>, IntoHttpError> {
let url = (base_url.to_owned() + METADATA.path) let url = (base_url.to_owned() + METADATA.path)
.replace(":room_alias", &self.room_alias.to_string()); .replace(":room_alias", &self.room_alias.to_string());
@ -51,7 +52,7 @@ impl OutgoingRequest for Request {
let http_request = http::Request::builder() let http_request = http::Request::builder()
.method(METADATA.method) .method(METADATA.method)
.uri(url) .uri(url)
.body(serde_json::to_vec(&request_body)?) .body(ruma_serde::json_to_buf(&request_body)?)
// this cannot fail because we don't give user-supplied data to any of the // this cannot fail because we don't give user-supplied data to any of the
// builder methods // builder methods
.unwrap(); .unwrap();
@ -113,10 +114,12 @@ impl IncomingResponse for Response {
} }
impl OutgoingResponse for Response { impl OutgoingResponse for Response {
fn try_into_http_response(self) -> Result<http::Response<Vec<u8>>, IntoHttpError> { fn try_into_http_response<T: Default + BufMut>(
self,
) -> Result<http::Response<T>, IntoHttpError> {
let response = http::Response::builder() let response = http::Response::builder()
.header(CONTENT_TYPE, "application/json") .header(CONTENT_TYPE, "application/json")
.body(b"{}".to_vec()) .body(ruma_serde::slice_to_buf(b"{}"))
.unwrap(); .unwrap();
Ok(response) Ok(response)

View File

@ -17,8 +17,9 @@ ruma_api! {
#[test] #[test]
fn empty_request_http_repr() { fn empty_request_http_repr() {
let req = Request {}; let req = Request {};
let http_req = let http_req = req
req.try_into_http_request("https://homeserver.tld", SendAccessToken::None).unwrap(); .try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None)
.unwrap();
assert!(http_req.body().is_empty()); assert!(http_req.body().is_empty());
} }
@ -26,7 +27,7 @@ fn empty_request_http_repr() {
#[test] #[test]
fn empty_response_http_repr() { fn empty_response_http_repr() {
let res = Response {}; let res = Response {};
let http_res = res.try_into_http_response().unwrap(); let http_res = res.try_into_http_response::<Vec<u8>>().unwrap();
assert_eq!(http_res.body(), b"{}"); assert_eq!(http_res.body(), b"{}");
} }

View File

@ -1,3 +1,4 @@
use bytes::BufMut;
use ruma_api::{ use ruma_api::{
error::{FromHttpResponseError, IntoHttpError, Void}, error::{FromHttpResponseError, IntoHttpError, Void},
ruma_api, IncomingResponse, OutgoingResponse, ruma_api, IncomingResponse, OutgoingResponse,
@ -35,7 +36,9 @@ impl IncomingResponse for Response {
} }
impl OutgoingResponse for Response { impl OutgoingResponse for Response {
fn try_into_http_response(self) -> Result<http::Response<Vec<u8>>, IntoHttpError> { fn try_into_http_response<T: Default + BufMut>(
self,
) -> Result<http::Response<T>, IntoHttpError> {
todo!() todo!()
} }
} }

View File

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

View File

@ -2,13 +2,14 @@
use std::{collections::BTreeMap, fmt, time::Duration}; use std::{collections::BTreeMap, fmt, time::Duration};
use bytes::BufMut;
use ruma_api::{ use ruma_api::{
error::{IntoHttpError, ResponseDeserializationError}, error::{IntoHttpError, ResponseDeserializationError},
EndpointError, OutgoingResponse, EndpointError, OutgoingResponse,
}; };
use ruma_identifiers::RoomVersionId; use ruma_identifiers::RoomVersionId;
use serde::{Deserialize, Serialize}; 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_slice as from_json_slice, Value as JsonValue};
/// Deserialize and Serialize implementations for ErrorKind. /// Deserialize and Serialize implementations for ErrorKind.
/// Separate module because it's a lot of code. /// Separate module because it's a lot of code.
@ -236,11 +237,13 @@ impl ErrorBody {
} }
impl OutgoingResponse for Error { impl OutgoingResponse for Error {
fn try_into_http_response(self) -> Result<http::Response<Vec<u8>>, IntoHttpError> { fn try_into_http_response<T: Default + BufMut>(
self,
) -> Result<http::Response<T>, IntoHttpError> {
http::Response::builder() http::Response::builder()
.header(http::header::CONTENT_TYPE, "application/json") .header(http::header::CONTENT_TYPE, "application/json")
.status(self.status_code) .status(self.status_code)
.body(to_json_vec(&ErrorBody::from(self))?) .body(ruma_serde::json_to_buf(&ErrorBody::from(self))?)
.map_err(Into::into) .map_err(Into::into)
} }
} }

View File

@ -84,7 +84,10 @@ mod tests {
since: Some("hello"), since: Some("hello"),
server: Some(&server_name!("test.tld")), server: Some(&server_name!("test.tld")),
} }
.try_into_http_request("https://homeserver.tld", SendAccessToken::IfRequired("auth_tok")) .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"),
)
.unwrap(); .unwrap();
let uri = req.uri(); let uri = req.uri();
@ -107,7 +110,7 @@ mod tests {
prev_batch: Some("prev_batch_token".into()), prev_batch: Some("prev_batch_token".into()),
total_room_count_estimate: Some(uint!(10)), total_room_count_estimate: Some(uint!(10)),
} }
.try_into_http_response() .try_into_http_response::<Vec<u8>>()
.unwrap(); .unwrap();
assert_eq!( assert_eq!(

View File

@ -191,7 +191,7 @@ mod tests {
}; };
let request = req let request = req
.try_into_http_request( .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld", "https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"), SendAccessToken::IfRequired("auth_tok"),
) )

View File

@ -60,7 +60,7 @@ mod tests {
#[test] #[test]
fn serialize_sso_login_with_provider_request_uri() { fn serialize_sso_login_with_provider_request_uri() {
let req = Request { idp_id: "provider", redirect_url: "https://example.com/sso" } let req = Request { idp_id: "provider", redirect_url: "https://example.com/sso" }
.try_into_http_request("https://homeserver.tld", SendAccessToken::None) .try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(

View File

@ -1,5 +1,6 @@
//! [GET /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}](https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey) //! [GET /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}](https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey)
use bytes::BufMut;
use ruma_api::{ruma_api, SendAccessToken}; use ruma_api::{ruma_api, SendAccessToken};
use ruma_events::{AnyStateEventContent, EventType}; use ruma_events::{AnyStateEventContent, EventType};
use ruma_identifiers::RoomId; use ruma_identifiers::RoomId;
@ -65,14 +66,14 @@ impl<'a> ruma_api::OutgoingRequest for Request<'a> {
const METADATA: ruma_api::Metadata = METADATA; const METADATA: ruma_api::Metadata = METADATA;
fn try_into_http_request( fn try_into_http_request<T: Default + BufMut>(
self, self,
base_url: &str, base_url: &str,
access_token: SendAccessToken<'_>, access_token: SendAccessToken<'_>,
) -> Result<http::Request<Vec<u8>>, ruma_api::error::IntoHttpError> { ) -> Result<http::Request<T>, ruma_api::error::IntoHttpError> {
use std::borrow::Cow; use std::borrow::Cow;
use http::header::{HeaderValue, AUTHORIZATION, CONTENT_TYPE}; use http::header::{self, HeaderValue};
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
let mut url = format!( let mut url = format!(
@ -90,9 +91,9 @@ impl<'a> ruma_api::OutgoingRequest for Request<'a> {
http::Request::builder() http::Request::builder()
.method(http::Method::GET) .method(http::Method::GET)
.uri(url) .uri(url)
.header(CONTENT_TYPE, "application/json") .header(header::CONTENT_TYPE, "application/json")
.header( .header(
AUTHORIZATION, header::AUTHORIZATION,
HeaderValue::from_str(&format!( HeaderValue::from_str(&format!(
"Bearer {}", "Bearer {}",
access_token access_token
@ -100,7 +101,7 @@ impl<'a> ruma_api::OutgoingRequest for Request<'a> {
.ok_or(ruma_api::error::IntoHttpError::NeedsAuthentication)?, .ok_or(ruma_api::error::IntoHttpError::NeedsAuthentication)?,
))?, ))?,
) )
.body(Vec::new()) .body(T::default())
.map_err(Into::into) .map_err(Into::into)
} }
} }

View File

@ -62,7 +62,7 @@ mod server_tests {
tags.insert("u.user_tag".into(), assign!(TagInfo::new(), { order: Some(0.11) })); tags.insert("u.user_tag".into(), assign!(TagInfo::new(), { order: Some(0.11) }));
let response = Response { tags }; let response = Response { tags };
let http_response = response.try_into_http_response().unwrap(); let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
let json_response: serde_json::Value = let json_response: serde_json::Value =
serde_json::from_slice(http_response.body()).unwrap(); serde_json::from_slice(http_response.body()).unwrap();

View File

@ -2,6 +2,7 @@
use std::{collections::BTreeMap, fmt}; use std::{collections::BTreeMap, fmt};
use bytes::BufMut;
use ruma_api::{ use ruma_api::{
error::{IntoHttpError, ResponseDeserializationError}, error::{IntoHttpError, ResponseDeserializationError},
EndpointError, OutgoingResponse, EndpointError, OutgoingResponse,
@ -9,8 +10,7 @@ use ruma_api::{
use ruma_serde::Outgoing; use ruma_serde::Outgoing;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{ use serde_json::{
from_slice as from_json_slice, to_vec as to_json_vec, value::RawValue as RawJsonValue, from_slice as from_json_slice, value::RawValue as RawJsonValue, Value as JsonValue,
Value as JsonValue,
}; };
use crate::error::{Error as MatrixError, ErrorBody}; use crate::error::{Error as MatrixError, ErrorBody};
@ -150,12 +150,14 @@ impl EndpointError for UiaaResponse {
impl std::error::Error for UiaaResponse {} impl std::error::Error for UiaaResponse {}
impl OutgoingResponse for UiaaResponse { impl OutgoingResponse for UiaaResponse {
fn try_into_http_response(self) -> Result<http::Response<Vec<u8>>, IntoHttpError> { fn try_into_http_response<T: Default + BufMut>(
self,
) -> Result<http::Response<T>, IntoHttpError> {
match self { match self {
UiaaResponse::AuthResponse(authentication_info) => http::Response::builder() UiaaResponse::AuthResponse(authentication_info) => http::Response::builder()
.header(http::header::CONTENT_TYPE, "application/json") .header(http::header::CONTENT_TYPE, "application/json")
.status(&http::StatusCode::UNAUTHORIZED) .status(&http::StatusCode::UNAUTHORIZED)
.body(to_json_vec(&authentication_info)?) .body(ruma_serde::json_to_buf(&authentication_info)?)
.map_err(Into::into), .map_err(Into::into),
UiaaResponse::MatrixError(error) => error.try_into_http_response(), UiaaResponse::MatrixError(error) => error.try_into_http_response(),
} }
@ -336,7 +338,8 @@ mod tests {
session: None, session: None,
auth_error: None, auth_error: None,
}; };
let uiaa_response = UiaaResponse::AuthResponse(uiaa_info).try_into_http_response().unwrap(); let uiaa_response =
UiaaResponse::AuthResponse(uiaa_info).try_into_http_response::<Vec<u8>>().unwrap();
assert_matches!( assert_matches!(
from_json_slice::<UiaaInfo>(uiaa_response.body()).unwrap(), from_json_slice::<UiaaInfo>(uiaa_response.body()).unwrap(),

View File

@ -357,7 +357,10 @@ impl Client {
SendAccessToken::None SendAccessToken::None
}; };
request.try_into_http_request(&client.homeserver_url.to_string(), access_token)? request.try_into_http_request::<Vec<u8>>(
&client.homeserver_url.to_string(),
access_token,
)?
}; };
let extra_params = urlencoded::to_string(extra_params).unwrap(); let extra_params = urlencoded::to_string(extra_params).unwrap();

View File

@ -57,7 +57,7 @@ mod tests {
#[test] #[test]
fn response_body() { fn response_body() {
let res = Response::new().try_into_http_response().unwrap(); let res = Response::new().try_into_http_response::<Vec<u8>>().unwrap();
assert_eq!(res.body(), b"{}"); assert_eq!(res.body(), b"{}");
} }

View File

@ -13,6 +13,7 @@ version = "0.3.1"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
bytes = "1.0.1"
form_urlencoded = "1.0.0" form_urlencoded = "1.0.0"
js_int = { version = "0.2.0", features = ["serde"] } js_int = { version = "0.2.0", features = ["serde"] }
itoa = "0.4.6" itoa = "0.4.6"

16
ruma-serde/src/buf.rs Normal file
View File

@ -0,0 +1,16 @@
use bytes::BufMut;
use serde::Serialize;
/// Converts a byte slice to a buffer by copying.
pub fn slice_to_buf<B: Default + BufMut>(s: &[u8]) -> B {
let mut buf = B::default();
buf.put_slice(s);
buf
}
/// Creates a buffer and writes a serializable value to it.
pub fn json_to_buf<B: Default + BufMut, T: Serialize>(val: &T) -> serde_json::Result<B> {
let mut buf = B::default().writer();
serde_json::to_writer(&mut buf, val)?;
Ok(buf.into_inner())
}

View File

@ -2,6 +2,7 @@
#![doc(html_logo_url = "https://www.ruma.io/images/logo.png")] #![doc(html_logo_url = "https://www.ruma.io/images/logo.png")]
//! (De)serialization helpers for other ruma crates. //! (De)serialization helpers for other ruma crates.
mod buf;
pub mod can_be_empty; pub mod can_be_empty;
mod canonical_json; mod canonical_json;
mod cow; mod cow;
@ -15,6 +16,7 @@ pub mod test;
pub mod time; pub mod time;
pub mod urlencoded; pub mod urlencoded;
pub use buf::{json_to_buf, slice_to_buf};
pub use can_be_empty::{is_empty, CanBeEmpty}; pub use can_be_empty::{is_empty, CanBeEmpty};
pub use canonical_json::{ pub use canonical_json::{
to_canonical_value, to_string as to_canonical_json_string, try_from_json_map, to_canonical_value, to_string as to_canonical_json_string, try_from_json_map,