Repository: ruma/ruma
Pull request #532 (2021-04-22 18:55:43)

Make try_into_http_{request,response} generic


Patch:
diff --git a/ruma-api-macros/src/api/request/outgoing.rs b/ruma-api-macros/src/api/request/outgoing.rs
index 46aed9b6..f53d5cd5 100644
--- a/ruma-api-macros/src/api/request/outgoing.rs
+++ b/ruma-api-macros/src/api/request/outgoing.rs
@@ -11,10 +11,10 @@ impl Request {
         lifetimes: &TokenStream,
         ruma_api: &TokenStream,
     ) -> TokenStream {
+        let bytes = quote! { #ruma_api::exports::bytes };
         let http = quote! { #ruma_api::exports::http };
         let percent_encoding = quote! { #ruma_api::exports::percent_encoding };
         let ruma_serde = quote! { #ruma_api::exports::ruma_serde };
-        let serde_json = quote! { #ruma_api::exports::serde_json };
 
         let method = &metadata.method;
         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 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() {
             let request_body_initializers = if let Some(field) = self.newtype_body_field() {
                 let field_name =
@@ -177,13 +177,10 @@ impl Request {
             };
 
             quote! {
-                {
-                    let request_body = RequestBody #request_body_initializers;
-                    #serde_json::to_vec(&request_body)?
-                }
+                #ruma_serde::json_to_buf(&RequestBody #request_body_initializers)?
             }
         } else {
-            quote! { Vec::new() }
+            quote! { <T as ::std::default::Default>::default() }
         };
 
         let non_auth_impls = metadata.authentication.iter().map(|auth| {
@@ -210,14 +207,11 @@ impl Request {
 
                 const METADATA: #ruma_api::Metadata = self::METADATA;
 
-                fn try_into_http_request(
+                fn try_into_http_request<T: ::std::default::Default + #bytes::BufMut>(
                     self,
                     base_url: &::std::primitive::str,
                     access_token: #ruma_api::SendAccessToken<'_>,
-                ) -> ::std::result::Result<
-                    #http::Request<::std::vec::Vec<::std::primitive::u8>>,
-                    #ruma_api::error::IntoHttpError,
-                > {
+                ) -> ::std::result::Result<#http::Request<T>, #ruma_api::error::IntoHttpError> {
                     let metadata = self::METADATA;
 
                     let mut req_builder = #http::Request::builder()
diff --git a/ruma-api-macros/src/api/response/outgoing.rs b/ruma-api-macros/src/api/response/outgoing.rs
index 937442c5..b9a0d7d4 100644
--- a/ruma-api-macros/src/api/response/outgoing.rs
+++ b/ruma-api-macros/src/api/response/outgoing.rs
@@ -5,8 +5,9 @@ use super::{Response, ResponseField};
 
 impl Response {
     pub fn expand_outgoing(&self, ruma_api: &TokenStream) -> TokenStream {
+        let bytes = quote! { #ruma_api::exports::bytes };
         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| {
             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 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() {
             let field_name = field.ident.as_ref().expect("expected field to have an identifier");
             quote! {
-                #serde_json::to_vec(&self.#field_name)?
+                #ruma_serde::json_to_buf(&self.#field_name)?
             }
         } else {
             let fields = self.fields.iter().map(|response_field| {
@@ -63,7 +64,7 @@ impl Response {
             });
 
             quote! {
-                #serde_json::to_vec(&ResponseBody { #(#fields)* })?
+                #ruma_serde::json_to_buf(&ResponseBody { #(#fields)* })?
             }
         };
 
@@ -72,12 +73,9 @@ impl Response {
             #[cfg(feature = "server")]
             #[allow(clippy::unknown_clippy_lints, clippy::inconsistent_struct_constructor)]
             impl #ruma_api::OutgoingResponse for Response {
-                fn try_into_http_response(
+                fn try_into_http_response<T: ::std::default::Default + #bytes::BufMut>(
                     self,
-                ) -> ::std::result::Result<
-                    #http::Response<::std::vec::Vec<::std::primitive::u8>>,
-                    #ruma_api::error::IntoHttpError,
-                > {
+                ) -> ::std::result::Result<#http::Response<T>, #ruma_api::error::IntoHttpError> {
                     let mut resp_builder = #http::Response::builder()
                         .header(#http::header::CONTENT_TYPE, "application/json");
 
diff --git a/ruma-api/Cargo.toml b/ruma-api/Cargo.toml
index b2f2ae17..c07adf13 100644
--- a/ruma-api/Cargo.toml
+++ b/ruma-api/Cargo.toml
@@ -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.4", path = "../ruma-api-macros" }
diff --git a/ruma-api/src/error.rs b/ruma-api/src/error.rs
index 154c3d42..d38c9204 100644
--- a/ruma-api/src/error.rs
+++ b/ruma-api/src/error.rs
@@ -4,6 +4,7 @@
 
 use std::{error::Error as StdError, fmt};
 
+use bytes::BufMut;
 use thiserror::Error;
 
 use crate::{EndpointError, OutgoingResponse};
@@ -14,7 +15,9 @@ use crate::{EndpointError, OutgoingResponse};
 pub enum 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 {}
     }
 }
diff --git a/ruma-api/src/lib.rs b/ruma-api/src/lib.rs
index 5a474533..0ba544cb 100644
--- a/ruma-api/src/lib.rs
+++ b/ruma-api/src/lib.rs
@@ -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 bytes::BufMut;
 use http::Method;
 use ruma_identifiers::UserId;
 
@@ -203,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;
@@ -267,11 +269,11 @@ pub trait OutgoingRequest: Sized {
     /// 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
     /// `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,
         base_url: &str,
         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.
@@ -291,12 +293,12 @@ pub trait OutgoingRequestAppserviceExt: OutgoingRequest {
     /// [assert Appservice identity][id_assert].
     ///
     /// [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,
         base_url: &str,
         access_token: SendAccessToken<'_>,
         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 user_id_query =
             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
     /// 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.
diff --git a/ruma-client-api/Cargo.toml b/ruma-client-api/Cargo.toml
index 66ea9070..093d8159 100644
--- a/ruma-client-api/Cargo.toml
+++ b/ruma-client-api/Cargo.toml
@@ -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"
diff --git a/ruma-client-api/src/error.rs b/ruma-client-api/src/error.rs
index f0e599e1..19382387 100644
--- a/ruma-client-api/src/error.rs
+++ b/ruma-client-api/src/error.rs
@@ -2,13 +2,14 @@
 
 use std::{collections::BTreeMap, fmt, time::Duration};
 
+use bytes::BufMut;
 use ruma_api::{
     error::{IntoHttpError, ResponseDeserializationError},
     EndpointError, OutgoingResponse,
 };
 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_slice as from_json_slice, Value as JsonValue};
 
 /// Deserialize and Serialize implementations for ErrorKind.
 /// Separate module because it's a lot of code.
@@ -236,11 +237,13 @@ impl ErrorBody {
 }
 
 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()
             .header(http::header::CONTENT_TYPE, "application/json")
             .status(self.status_code)
-            .body(to_json_vec(&ErrorBody::from(self))?)
+            .body(ruma_serde::json_to_buf(&ErrorBody::from(self))?)
             .map_err(Into::into)
     }
 }
diff --git a/ruma-client-api/src/r0/directory/get_public_rooms.rs b/ruma-client-api/src/r0/directory/get_public_rooms.rs
index 53afc040..d4daba8c 100644
--- a/ruma-client-api/src/r0/directory/get_public_rooms.rs
+++ b/ruma-client-api/src/r0/directory/get_public_rooms.rs
@@ -84,7 +84,10 @@ mod tests {
             since: Some("hello"),
             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();
 
         let uri = req.uri();
@@ -107,7 +110,7 @@ mod tests {
             prev_batch: Some("prev_batch_token".into()),
             total_room_count_estimate: Some(uint!(10)),
         }
-        .try_into_http_response()
+        .try_into_http_response::<Vec<u8>>()
         .unwrap();
 
         assert_eq!(
diff --git a/ruma-client-api/src/r0/message/get_message_events.rs b/ruma-client-api/src/r0/message/get_message_events.rs
index 9bc5646a..6d9f9c13 100644
--- a/ruma-client-api/src/r0/message/get_message_events.rs
+++ b/ruma-client-api/src/r0/message/get_message_events.rs
@@ -191,7 +191,7 @@ mod tests {
         };
 
         let request = req
-            .try_into_http_request(
+            .try_into_http_request::<Vec<u8>>(
                 "https://homeserver.tld",
                 SendAccessToken::IfRequired("auth_tok"),
             )
diff --git a/ruma-client-api/src/r0/session/sso_login_with_provider.rs b/ruma-client-api/src/r0/session/sso_login_with_provider.rs
index 135b8295..4a88cc71 100644
--- a/ruma-client-api/src/r0/session/sso_login_with_provider.rs
+++ b/ruma-client-api/src/r0/session/sso_login_with_provider.rs
@@ -60,7 +60,7 @@ mod tests {
     #[test]
     fn serialize_sso_login_with_provider_request_uri() {
         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();
 
         assert_eq!(
diff --git a/ruma-client-api/src/r0/state/get_state_events_for_key.rs b/ruma-client-api/src/r0/state/get_state_events_for_key.rs
index f4c341db..c4719b9b 100644
--- a/ruma-client-api/src/r0/state/get_state_events_for_key.rs
+++ b/ruma-client-api/src/r0/state/get_state_events_for_key.rs
@@ -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)
 
+use bytes::BufMut;
 use ruma_api::{ruma_api, SendAccessToken};
 use ruma_events::{AnyStateEventContent, EventType};
 use ruma_identifiers::RoomId;
@@ -65,14 +66,14 @@ impl<'a> ruma_api::OutgoingRequest for Request<'a> {
 
     const METADATA: ruma_api::Metadata = METADATA;
 
-    fn try_into_http_request(
+    fn try_into_http_request<T: Default + BufMut>(
         self,
         base_url: &str,
         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 http::header::{HeaderValue, AUTHORIZATION, CONTENT_TYPE};
+        use http::header::{self, HeaderValue};
         use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
 
         let mut url = format!(
@@ -90,9 +91,9 @@ impl<'a> ruma_api::OutgoingRequest for Request<'a> {
         http::Request::builder()
             .method(http::Method::GET)
             .uri(url)
-            .header(CONTENT_TYPE, "application/json")
+            .header(header::CONTENT_TYPE, "application/json")
             .header(
-                AUTHORIZATION,
+                header::AUTHORIZATION,
                 HeaderValue::from_str(&format!(
                     "Bearer {}",
                     access_token
@@ -100,7 +101,7 @@ impl<'a> ruma_api::OutgoingRequest for Request<'a> {
                         .ok_or(ruma_api::error::IntoHttpError::NeedsAuthentication)?,
                 ))?,
             )
-            .body(Vec::new())
+            .body(T::default())
             .map_err(Into::into)
     }
 }
diff --git a/ruma-client-api/src/r0/tag/get_tags.rs b/ruma-client-api/src/r0/tag/get_tags.rs
index 283c36cc..2e0d552e 100644
--- a/ruma-client-api/src/r0/tag/get_tags.rs
+++ b/ruma-client-api/src/r0/tag/get_tags.rs
@@ -62,7 +62,7 @@ mod server_tests {
         tags.insert("u.user_tag".into(), assign!(TagInfo::new(), { order: Some(0.11) }));
         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 =
             serde_json::from_slice(http_response.body()).unwrap();
diff --git a/ruma-client-api/src/r0/uiaa.rs b/ruma-client-api/src/r0/uiaa.rs
index f59bab42..f8690e68 100644
--- a/ruma-client-api/src/r0/uiaa.rs
+++ b/ruma-client-api/src/r0/uiaa.rs
@@ -2,6 +2,7 @@
 
 use std::{collections::BTreeMap, fmt};
 
+use bytes::BufMut;
 use ruma_api::{
     error::{IntoHttpError, ResponseDeserializationError},
     EndpointError, OutgoingResponse,
@@ -9,8 +10,7 @@ use ruma_api::{
 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,
-    Value as JsonValue,
+    from_slice as from_json_slice, value::RawValue as RawJsonValue, Value as JsonValue,
 };
 
 use crate::error::{Error as MatrixError, ErrorBody};
@@ -150,12 +150,14 @@ impl EndpointError for UiaaResponse {
 impl std::error::Error 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 {
             UiaaResponse::AuthResponse(authentication_info) => http::Response::builder()
                 .header(http::header::CONTENT_TYPE, "application/json")
                 .status(&http::StatusCode::UNAUTHORIZED)
-                .body(to_json_vec(&authentication_info)?)
+                .body(ruma_serde::json_to_buf(&authentication_info)?)
                 .map_err(Into::into),
             UiaaResponse::MatrixError(error) => error.try_into_http_response(),
         }
@@ -336,7 +338,8 @@ mod tests {
             session: 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!(
             from_json_slice::<UiaaInfo>(uiaa_response.body()).unwrap(),
diff --git a/ruma-client/src/lib.rs b/ruma-client/src/lib.rs
index 9299e83a..4ca38554 100644
--- a/ruma-client/src/lib.rs
+++ b/ruma-client/src/lib.rs
@@ -357,7 +357,10 @@ impl Client {
                 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();
diff --git a/ruma-federation-api/src/membership/create_leave_event/v2.rs b/ruma-federation-api/src/membership/create_leave_event/v2.rs
index acdf9c77..8052cd84 100644
--- a/ruma-federation-api/src/membership/create_leave_event/v2.rs
+++ b/ruma-federation-api/src/membership/create_leave_event/v2.rs
@@ -57,7 +57,7 @@ mod tests {
 
     #[test]
     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"{}");
     }
diff --git a/ruma-serde/Cargo.toml b/ruma-serde/Cargo.toml
index 0f77d7fd..491fc3be 100644
--- a/ruma-serde/Cargo.toml
+++ b/ruma-serde/Cargo.toml
@@ -13,6 +13,7 @@ version = "0.3.1"
 edition = "2018"
 
 [dependencies]
+bytes = "1.0.1"
 form_urlencoded = "1.0.0"
 js_int = { version = "0.2.0", features = ["serde"] }
 itoa = "0.4.6"
diff --git a/ruma-serde/src/buf.rs b/ruma-serde/src/buf.rs
new file mode 100644
index 00000000..1c597c6b
--- /dev/null
+++ b/ruma-serde/src/buf.rs
@@ -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())
+}
diff --git a/ruma-serde/src/lib.rs b/ruma-serde/src/lib.rs
index 4c71e7cf..6d137a57 100644
--- a/ruma-serde/src/lib.rs
+++ b/ruma-serde/src/lib.rs
@@ -2,6 +2,7 @@
 #![doc(html_logo_url = "https://www.ruma.io/images/logo.png")]
 //! (De)serialization helpers for other ruma crates.
 
+mod buf;
 pub mod can_be_empty;
 mod canonical_json;
 mod cow;
@@ -15,6 +16,7 @@ pub mod test;
 pub mod time;
 pub mod urlencoded;
 
+pub use buf::{json_to_buf, slice_to_buf};
 pub use can_be_empty::{is_empty, CanBeEmpty};
 pub use canonical_json::{
     to_canonical_value, to_string as to_canonical_json_string, try_from_json_map,


Test patch:
diff --git a/ruma-api/tests/conversions.rs b/ruma-api/tests/conversions.rs
index 14e941d5..b521279e 100644
--- a/ruma-api/tests/conversions.rs
+++ b/ruma-api/tests/conversions.rs
@@ -48,8 +48,10 @@ fn request_serde() {
         user: user_id!("@bazme:ruma.io"),
     };
 
-    let http_req =
-        req.clone().try_into_http_request("https://homeserver.tld", SendAccessToken::None).unwrap();
+    let http_req = req
+        .clone()
+        .try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None)
+        .unwrap();
     let req2 = Request::try_from_http_request(http_req).unwrap();
 
     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 http_req = req
-        .try_into_http_request_with_user_id(
+        .try_into_http_request_with_user_id::<Vec<u8>>(
             "https://homeserver.tld",
             SendAccessToken::None,
             user_id,
@@ -131,7 +133,7 @@ mod without_query {
 
         let user_id = user_id!("@_virtual_:ruma.io");
         let http_req = req
-            .try_into_http_request_with_user_id(
+            .try_into_http_request_with_user_id::<Vec<u8>>(
                 "https://homeserver.tld",
                 SendAccessToken::None,
                 user_id,
diff --git a/ruma-api/tests/header_override.rs b/ruma-api/tests/header_override.rs
index e3dd9102..26673996 100644
--- a/ruma-api/tests/header_override.rs
+++ b/ruma-api/tests/header_override.rs
@@ -28,7 +28,7 @@ ruma_api! {
 #[test]
 fn response_content_type_override() {
     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,
     // not adding another content-type header.
@@ -45,8 +45,9 @@ fn response_content_type_override() {
 #[test]
 fn request_content_type_override() {
     let req = Request { location: None, stuff: "magic".into() };
-    let mut http_req =
-        req.try_into_http_request("https://homeserver.tld", SendAccessToken::None).unwrap();
+    let mut http_req = req
+        .try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None)
+        .unwrap();
 
     assert_eq!(
         match http_req.headers_mut().entry(CONTENT_TYPE) {
diff --git a/ruma-api/tests/manual_endpoint_impl.rs b/ruma-api/tests/manual_endpoint_impl.rs
index 19a886cc..43bf5b84 100644
--- a/ruma-api/tests/manual_endpoint_impl.rs
+++ b/ruma-api/tests/manual_endpoint_impl.rs
@@ -2,6 +2,7 @@
 
 use std::convert::TryFrom;
 
+use bytes::BufMut;
 use http::{header::CONTENT_TYPE, method::Method};
 use ruma_api::{
     error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError, ServerError, Void},
@@ -38,11 +39,11 @@ impl OutgoingRequest for Request {
 
     const METADATA: Metadata = METADATA;
 
-    fn try_into_http_request(
+    fn try_into_http_request<T: Default + BufMut>(
         self,
         base_url: &str,
         _access_token: SendAccessToken<'_>,
-    ) -> Result<http::Request<Vec<u8>>, IntoHttpError> {
+    ) -> Result<http::Request<T>, IntoHttpError> {
         let url = (base_url.to_owned() + METADATA.path)
             .replace(":room_alias", &self.room_alias.to_string());
 
@@ -51,7 +52,7 @@ impl OutgoingRequest for Request {
         let http_request = http::Request::builder()
             .method(METADATA.method)
             .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
             // builder methods
             .unwrap();
@@ -113,10 +114,12 @@ impl IncomingResponse 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()
             .header(CONTENT_TYPE, "application/json")
-            .body(b"{}".to_vec())
+            .body(ruma_serde::slice_to_buf(b"{}"))
             .unwrap();
 
         Ok(response)
diff --git a/ruma-api/tests/no_fields.rs b/ruma-api/tests/no_fields.rs
index 925a1cae..17bdb55f 100644
--- a/ruma-api/tests/no_fields.rs
+++ b/ruma-api/tests/no_fields.rs
@@ -17,8 +17,9 @@ ruma_api! {
 #[test]
 fn empty_request_http_repr() {
     let req = Request {};
-    let http_req =
-        req.try_into_http_request("https://homeserver.tld", SendAccessToken::None).unwrap();
+    let http_req = req
+        .try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None)
+        .unwrap();
 
     assert!(http_req.body().is_empty());
 }
@@ -26,7 +27,7 @@ fn empty_request_http_repr() {
 #[test]
 fn empty_response_http_repr() {
     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"{}");
 }
diff --git a/ruma-api/tests/ui/05-request-only.rs b/ruma-api/tests/ui/05-request-only.rs
index 9e01fc95..61579fca 100644
--- a/ruma-api/tests/ui/05-request-only.rs
+++ b/ruma-api/tests/ui/05-request-only.rs
@@ -1,3 +1,4 @@
+use bytes::BufMut;
 use ruma_api::{
     error::{FromHttpResponseError, IntoHttpError, Void},
     ruma_api, IncomingResponse, OutgoingResponse,
@@ -35,7 +36,9 @@ impl IncomingResponse 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!()
     }
 }