api: Add multiple path support to OutgoingRequest

This commit is contained in:
Jonathan de Jong 2022-02-12 13:44:11 +01:00 committed by GitHub
parent dad84ac1a0
commit 397727726e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 475 additions and 117 deletions

View File

@ -172,6 +172,7 @@ impl Parse for Metadata {
// TODO replace with error // TODO replace with error
// return Err(syn::Error::new_spanned(metadata_kw, "no path is defined")); // return Err(syn::Error::new_spanned(metadata_kw, "no path is defined"));
r0_path = path.clone(); r0_path = path.clone();
unstable_path = path.clone();
} }
Ok(Self { Ok(Self {

View File

@ -81,7 +81,9 @@ impl Request {
let struct_attributes = &self.attributes; let struct_attributes = &self.attributes;
let method = &metadata.method; let method = &metadata.method;
let path = &metadata.path; let unstable_attr = metadata.unstable_path.as_ref().map(|p| quote! { unstable = #p, });
let r0_attr = metadata.r0_path.as_ref().map(|p| quote! { r0 = #p, });
let stable_attr = metadata.stable_path.as_ref().map(|p| quote! { stable = #p, });
let auth_attributes = metadata.authentication.iter().map(|field| { let auth_attributes = metadata.authentication.iter().map(|field| {
let cfg_expr = all_cfgs_expr(&field.attrs); let cfg_expr = all_cfgs_expr(&field.attrs);
let value = &field.value; let value = &field.value;
@ -110,7 +112,9 @@ impl Request {
#[incoming_derive(!Deserialize, #ruma_api_macros::_FakeDeriveRumaApi)] #[incoming_derive(!Deserialize, #ruma_api_macros::_FakeDeriveRumaApi)]
#[ruma_api( #[ruma_api(
method = #method, method = #method,
path = #path, #unstable_attr
#r0_attr
#stable_attr
error_ty = #error_ty, error_ty = #error_ty,
)] )]
#( #auth_attributes )* #( #auth_attributes )*

View File

@ -52,7 +52,9 @@ pub fn expand_derive_request(input: DeriveInput) -> syn::Result<TokenStream> {
let mut authentication = None; let mut authentication = None;
let mut error_ty = None; let mut error_ty = None;
let mut method = None; let mut method = None;
let mut path = None; let mut unstable_path = None;
let mut r0_path = None;
let mut stable_path = None;
for attr in input.attrs { for attr in input.attrs {
if !attr.path.is_ident("ruma_api") { if !attr.path.is_ident("ruma_api") {
@ -71,8 +73,14 @@ pub fn expand_derive_request(input: DeriveInput) -> syn::Result<TokenStream> {
MetaValue::Type(t) if name == "error_ty" => { MetaValue::Type(t) if name == "error_ty" => {
error_ty = Some(t); error_ty = Some(t);
} }
MetaValue::Lit(Lit::Str(s)) if name == "path" => { MetaValue::Lit(Lit::Str(s)) if name == "unstable" => {
path = Some(s); unstable_path = Some(s);
}
MetaValue::Lit(Lit::Str(s)) if name == "r0" => {
r0_path = Some(s);
}
MetaValue::Lit(Lit::Str(s)) if name == "stable" => {
stable_path = Some(s);
} }
_ => unreachable!("invalid ruma_api({}) attribute", name), _ => unreachable!("invalid ruma_api({}) attribute", name),
} }
@ -86,7 +94,9 @@ pub fn expand_derive_request(input: DeriveInput) -> syn::Result<TokenStream> {
lifetimes, lifetimes,
authentication: authentication.expect("missing authentication attribute"), authentication: authentication.expect("missing authentication attribute"),
method: method.expect("missing method attribute"), method: method.expect("missing method attribute"),
path: path.expect("missing path attribute"), unstable_path,
r0_path,
stable_path,
error_ty: error_ty.expect("missing error_ty attribute"), error_ty: error_ty.expect("missing error_ty attribute"),
}; };
@ -110,7 +120,9 @@ struct Request {
authentication: AuthScheme, authentication: AuthScheme,
method: Ident, method: Ident,
path: LitStr, unstable_path: Option<LitStr>,
r0_path: Option<LitStr>,
stable_path: Option<LitStr>,
error_ty: Type, error_ty: Type,
} }

View File

@ -1,8 +1,8 @@
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::Field; use syn::{Field, LitStr};
use crate::auth_scheme::AuthScheme; use crate::{auth_scheme::AuthScheme, util};
use super::{Request, RequestField}; use super::{Request, RequestField};
@ -15,39 +15,29 @@ impl Request {
let method = &self.method; let method = &self.method;
let error_ty = &self.error_ty; let error_ty = &self.error_ty;
let request_path_string = if self.has_path_fields() {
let mut format_string = self.path.value();
let mut format_args = Vec::new();
while let Some(start_of_segment) = format_string.find(':') { let (unstable_path, r0_path, stable_path) = if self.has_path_fields() {
// ':' should only ever appear at the start of a segment let path_format_args_call_with_percent_encoding = |s: &LitStr| -> TokenStream {
assert_eq!(&format_string[start_of_segment - 1..start_of_segment], "/"); util::path_format_args_call(s.value(), &percent_encoding)
let end_of_segment = match format_string[start_of_segment..].find('/') {
Some(rel_pos) => start_of_segment + rel_pos,
None => format_string.len(),
}; };
let path_var = Ident::new( (
&format_string[start_of_segment + 1..end_of_segment], self.unstable_path.as_ref().map(path_format_args_call_with_percent_encoding),
Span::call_site(), self.r0_path.as_ref().map(path_format_args_call_with_percent_encoding),
); self.stable_path.as_ref().map(path_format_args_call_with_percent_encoding),
format_args.push(quote! {
#percent_encoding::utf8_percent_encode(
&::std::string::ToString::to_string(&self.#path_var),
#percent_encoding::NON_ALPHANUMERIC,
) )
});
format_string.replace_range(start_of_segment..end_of_segment, "{}");
}
quote! {
format_args!(#format_string, #(#format_args),*)
}
} else { } else {
quote! { metadata.path.to_owned() } (
self.unstable_path.as_ref().map(|path| quote! { format_args!(#path) }),
self.r0_path.as_ref().map(|path| quote! { format_args!(#path) }),
self.stable_path.as_ref().map(|path| quote! { format_args!(#path) }),
)
}; };
let unstable_path = util::map_option_literal(&unstable_path);
let r0_path = util::map_option_literal(&r0_path);
let stable_path = util::map_option_literal(&stable_path);
let request_query_string = if let Some(field) = self.query_map_field() { let request_query_string = if let Some(field) = self.query_map_field() {
let field_name = field.ident.as_ref().expect("expected field to have identifier"); let field_name = field.ident.as_ref().expect("expected field to have identifier");
@ -206,6 +196,7 @@ impl Request {
self, self,
base_url: &::std::primitive::str, base_url: &::std::primitive::str,
access_token: #ruma_api::SendAccessToken<'_>, access_token: #ruma_api::SendAccessToken<'_>,
considering_versions: &'_ [#ruma_api::MatrixVersion],
) -> ::std::result::Result<#http::Request<T>, #ruma_api::error::IntoHttpError> { ) -> ::std::result::Result<#http::Request<T>, #ruma_api::error::IntoHttpError> {
let metadata = self::METADATA; let metadata = self::METADATA;
@ -214,7 +205,7 @@ impl Request {
.uri(::std::format!( .uri(::std::format!(
"{}{}{}", "{}{}{}",
base_url.strip_suffix('/').unwrap_or(base_url), base_url.strip_suffix('/').unwrap_or(base_url),
#request_path_string, #ruma_api::select_path(considering_versions, &metadata, #unstable_path, #r0_path, #stable_path)?,
#request_query_string, #request_query_string,
)); ));

View File

@ -2,7 +2,7 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use proc_macro2::TokenStream; use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_crate::{crate_name, FoundCrate}; use proc_macro_crate::{crate_name, FoundCrate};
use quote::{format_ident, quote, ToTokens}; use quote::{format_ident, quote, ToTokens};
use syn::{parse_quote, visit::Visit, AttrStyle, Attribute, Lifetime, NestedMeta, Type}; use syn::{parse_quote, visit::Visit, AttrStyle, Attribute, Lifetime, NestedMeta, Type};
@ -77,3 +77,34 @@ pub fn extract_cfg(attr: &Attribute) -> Option<NestedMeta> {
Some(list.nested.pop().unwrap().into_value()) Some(list.nested.pop().unwrap().into_value())
} }
pub fn path_format_args_call(
mut format_string: String,
percent_encoding: &TokenStream,
) -> TokenStream {
let mut format_args = Vec::new();
while let Some(start_of_segment) = format_string.find(':') {
// ':' should only ever appear at the start of a segment
assert_eq!(&format_string[start_of_segment - 1..start_of_segment], "/");
let end_of_segment = match format_string[start_of_segment..].find('/') {
Some(rel_pos) => start_of_segment + rel_pos,
None => format_string.len(),
};
let path_var =
Ident::new(&format_string[start_of_segment + 1..end_of_segment], Span::call_site());
format_args.push(quote! {
#percent_encoding::utf8_percent_encode(
&::std::string::ToString::to_string(&self.#path_var),
#percent_encoding::NON_ALPHANUMERIC,
)
});
format_string.replace_range(start_of_segment..end_of_segment, "{}");
}
quote! {
format_args!(#format_string, #(#format_args),*)
}
}

View File

@ -35,7 +35,9 @@ ruma-serde = { version = "0.5.0", path = "../ruma-serde" }
serde = { version = "1.0.118", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
serde_json = "1.0.61" serde_json = "1.0.61"
thiserror = "1.0.26" thiserror = "1.0.26"
tracing = "0.1.25"
[dev-dependencies] [dev-dependencies]
matches = "0.1.8"
ruma-events = { version = "0.24.6", path = "../ruma-events" } ruma-events = { version = "0.24.6", path = "../ruma-events" }
trybuild = "1.0.38" trybuild = "1.0.38"

View File

@ -8,7 +8,7 @@ use bytes::BufMut;
use serde_json::{from_slice as from_json_slice, Value as JsonValue}; use serde_json::{from_slice as from_json_slice, Value as JsonValue};
use thiserror::Error; use thiserror::Error;
use crate::{EndpointError, OutgoingResponse}; use crate::{EndpointError, MatrixVersion, OutgoingResponse};
/// A general-purpose Matrix error type consisting of an HTTP status code and a JSON body. /// A general-purpose Matrix error type consisting of an HTTP status code and a JSON body.
/// ///
@ -67,6 +67,18 @@ pub enum IntoHttpError {
)] )]
NeedsAuthentication, NeedsAuthentication,
/// Tried to create a request with an old enough version, for which no unstable endpoint
/// exists.
///
/// This is also a fallback error for if the version is too new for this endpoint.
#[error("Endpoint was not supported by server-reported versions, but no unstable path to fall back to was defined.")]
NoUnstablePath,
/// Tried to create a request with [`MatrixVersion`]s for all of which this endpoint was
/// removed.
#[error("Could not create any path variant for endpoint, as it was removed in version {0}")]
EndpointRemoved(MatrixVersion),
/// JSON serialization failed. /// JSON serialization failed.
#[error("JSON serialization failed: {0}")] #[error("JSON serialization failed: {0}")]
Json(#[from] serde_json::Error), Json(#[from] serde_json::Error),

View File

@ -18,7 +18,7 @@
#[cfg(not(all(feature = "client", feature = "server")))] #[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"); compile_error!("ruma_api's Cargo features only exist as a workaround are not meant to be disabled");
use std::{convert::TryInto as _, error::Error as StdError}; use std::{convert::TryInto as _, error::Error as StdError, fmt};
use bytes::BufMut; use bytes::BufMut;
use ruma_identifiers::UserId; use ruma_identifiers::UserId;
@ -267,8 +267,18 @@ pub trait OutgoingRequest: Sized {
/// Tries to convert this request into an `http::Request`. /// Tries to convert this request into an `http::Request`.
/// ///
/// This method should only fail when called on endpoints that require authentication. It may /// On endpoints with authentication, when adequate information isn't provided through
/// also fail with a serialization error in case of bugs in Ruma though. /// access_token, this could result in an error. It may also fail with a serialization error
/// in case of bugs in Ruma though.
///
/// It may also fail if, for every version in `considering_versions`;
/// - The endpoint is too old, and has been removed in all versions.
/// ([`EndpointRemoved`](error::IntoHttpError::EndpointRemoved))
/// - The endpoint is too new, and no unstable path is known for this endpoint.
/// ([`NoUnstablePath`](error::IntoHttpError::NoUnstablePath))
///
/// Finally, this will emit a warning through `tracing` if it detects if any version in
/// `considering_versions` has deprecated this endpoint.
/// ///
/// 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
@ -277,6 +287,7 @@ pub trait OutgoingRequest: Sized {
self, self,
base_url: &str, base_url: &str,
access_token: SendAccessToken<'_>, access_token: SendAccessToken<'_>,
considering_versions: &'_ [MatrixVersion],
) -> Result<http::Request<T>, IntoHttpError>; ) -> Result<http::Request<T>, IntoHttpError>;
} }
@ -302,8 +313,10 @@ pub trait OutgoingRequestAppserviceExt: OutgoingRequest {
base_url: &str, base_url: &str,
access_token: SendAccessToken<'_>, access_token: SendAccessToken<'_>,
user_id: &UserId, user_id: &UserId,
considering_versions: &'_ [MatrixVersion],
) -> Result<http::Request<T>, 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, considering_versions)?;
let user_id_query = ruma_serde::urlencoded::to_string(&[("user_id", user_id)])?; let user_id_query = ruma_serde::urlencoded::to_string(&[("user_id", user_id)])?;
let uri = http_request.uri().to_owned(); let uri = http_request.uri().to_owned();
@ -400,3 +413,66 @@ pub enum AuthScheme {
/// Authentication is performed by setting the `access_token` query parameter. /// Authentication is performed by setting the `access_token` query parameter.
QueryOnlyAccessToken, QueryOnlyAccessToken,
} }
// This function helps picks the right path (or an error) from a set of matrix versions.
//
// This function needs to be public, yet hidden, as all `try_into_http_request`s would be using it.
//
// Note this assumes that `versions`;
// - at least has 1 element
// - have all elements sorted by its `.into_parts` representation.
#[doc(hidden)]
pub fn select_path<'a>(
versions: &'_ [MatrixVersion],
metadata: &'_ Metadata,
unstable: Option<fmt::Arguments<'a>>,
r0: Option<fmt::Arguments<'a>>,
stable: Option<fmt::Arguments<'a>>,
) -> Result<fmt::Arguments<'a>, IntoHttpError> {
let greater_or_equal_any =
|version: MatrixVersion| versions.iter().any(|v| v.is_superset_of(version));
let greater_or_equal_all =
|version: MatrixVersion| versions.iter().all(|v| v.is_superset_of(version));
let is_stable_any = metadata.added.map(greater_or_equal_any).unwrap_or(false);
let is_removed_all = metadata.removed.map(greater_or_equal_all).unwrap_or(false);
// Only when all the versions (e.g. 1.6-9) are compatible with the version that added it (e.g.
// 1.1), yet are also "after" the version that removed it (e.g. 1.3), then we return that error.
// Otherwise, this all versions may fall into a different major range, such as 2.X, where
// "after" and "compatible" do not exist with the 1.X range, so we at least need to make sure
// that the versions are part of the same "range" through the `added` check.
if is_stable_any && is_removed_all {
return Err(IntoHttpError::EndpointRemoved(metadata.removed.unwrap()));
}
if is_stable_any {
let is_deprecated_any = metadata.deprecated.map(greater_or_equal_any).unwrap_or(false);
if is_deprecated_any {
let is_removed_any = metadata.removed.map(greater_or_equal_any).unwrap_or(false);
if is_removed_any {
tracing::warn!("endpoint {} is deprecated, but also removed in one of more server-passed versions: {:?}", metadata.name, versions)
} else {
tracing::warn!(
"endpoint {} is deprecated in one of more server-passed versions: {:?}",
metadata.name,
versions
)
}
}
if let Some(r0) = r0 {
if versions.iter().all(|&v| v == MatrixVersion::V1_0) {
// Endpoint was added in 1.0, we return the r0 variant.
return Ok(r0);
}
}
return Ok(stable.expect("metadata.added enforces the stable path to exist"));
}
unstable.ok_or(IntoHttpError::NoUnstablePath)
}

View File

@ -50,15 +50,15 @@ pub struct Metadata {
/// The matrix version that deprecated this endpoint. /// The matrix version that deprecated this endpoint.
/// ///
/// Deprecation often precedes one matrix version before removal. /// Deprecation often precedes one matrix version before removal.
// TODO add once try_into_http_request has been altered; ///
// This will make `try_into_http_request` emit a warning, /// This will make [`try_into_http_request`](crate::OutgoingRequest::try_into_http_request)
// see the corresponding documentation for more information. /// emit a warning, see the corresponding documentation for more information.
pub deprecated: Option<MatrixVersion>, pub deprecated: Option<MatrixVersion>,
/// The matrix version that removed this endpoint. /// The matrix version that removed this endpoint.
// TODO add once try_into_http_request has been altered; ///
// This will make `try_into_http_request` emit an error, /// This will make [`try_into_http_request`](crate::OutgoingRequest::try_into_http_request)
// see the corresponding documentation for more information. /// emit an error, see the corresponding documentation for more information.
pub removed: Option<MatrixVersion>, pub removed: Option<MatrixVersion>,
} }
@ -71,10 +71,11 @@ pub struct Metadata {
/// backwards-compatible manner. /// backwards-compatible manner.
/// ///
/// Matrix has a deprecation policy, read more about it here: <https://spec.matrix.org/v1.2/#deprecation-policy>. /// Matrix has a deprecation policy, read more about it here: <https://spec.matrix.org/v1.2/#deprecation-policy>.
// TODO add the following once `EndpointPath` and added/deprecated/removed macros are in place; ///
// Ruma keeps track of when endpoints are added, deprecated, and removed. It'll automatically /// Ruma keeps track of when endpoints are added, deprecated, and removed. It'll automatically
// select the right endpoint stability variation to use depending on which Matrix version you /// select the right endpoint stability variation to use depending on which Matrix versions you
// pass it with [`EndpointPath`], see its respective documentation for more. /// pass to [`try_into_http_request`](crate::OutgoingRequest::try_into_http_request), see its
/// respective documentation for more information.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum MatrixVersion { pub enum MatrixVersion {

View File

@ -1,8 +1,8 @@
#![allow(clippy::exhaustive_structs)] #![allow(clippy::exhaustive_structs)]
use ruma_api::{ use ruma_api::{
ruma_api, IncomingRequest as _, OutgoingRequest as _, OutgoingRequestAppserviceExt, ruma_api, IncomingRequest as _, MatrixVersion, OutgoingRequest as _,
SendAccessToken, OutgoingRequestAppserviceExt, SendAccessToken,
}; };
use ruma_identifiers::{user_id, UserId}; use ruma_identifiers::{user_id, UserId};
@ -52,7 +52,11 @@ fn request_serde() {
let http_req = req let http_req = req
.clone() .clone()
.try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None) .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld",
SendAccessToken::None,
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
let req2 = Request::try_from_http_request(http_req, &["barVal", "@bazme:ruma.io"]).unwrap(); let req2 = Request::try_from_http_request(http_req, &["barVal", "@bazme:ruma.io"]).unwrap();
@ -75,7 +79,11 @@ fn invalid_uri_should_not_panic() {
user: user_id!("@bazme:ruma.io").to_owned(), user: user_id!("@bazme:ruma.io").to_owned(),
}; };
let result = req.try_into_http_request::<Vec<u8>>("invalid uri", SendAccessToken::None); let result = req.try_into_http_request::<Vec<u8>>(
"invalid uri",
SendAccessToken::None,
&[MatrixVersion::V1_0],
);
assert!(result.is_err()); assert!(result.is_err());
} }
@ -96,6 +104,7 @@ fn request_with_user_id_serde() {
"https://homeserver.tld", "https://homeserver.tld",
SendAccessToken::None, SendAccessToken::None,
user_id, user_id,
&[MatrixVersion::V1_0],
) )
.unwrap(); .unwrap();
@ -108,6 +117,8 @@ fn request_with_user_id_serde() {
} }
mod without_query { mod without_query {
use ruma_api::MatrixVersion;
use super::{ruma_api, user_id, OutgoingRequestAppserviceExt, SendAccessToken, UserId}; use super::{ruma_api, user_id, OutgoingRequestAppserviceExt, SendAccessToken, UserId};
ruma_api! { ruma_api! {
@ -154,6 +165,7 @@ mod without_query {
"https://homeserver.tld", "https://homeserver.tld",
SendAccessToken::None, SendAccessToken::None,
user_id, user_id,
&[MatrixVersion::V1_0],
) )
.unwrap(); .unwrap();

View File

@ -1,7 +1,9 @@
#![allow(clippy::exhaustive_structs)] #![allow(clippy::exhaustive_structs)]
use http::header::{Entry, CONTENT_TYPE}; use http::header::{Entry, CONTENT_TYPE};
use ruma_api::{ruma_api, OutgoingRequest as _, OutgoingResponse as _, SendAccessToken}; use ruma_api::{
ruma_api, MatrixVersion, OutgoingRequest as _, OutgoingResponse as _, SendAccessToken,
};
ruma_api! { ruma_api! {
metadata: { metadata: {
@ -48,7 +50,11 @@ fn response_content_type_override() {
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 = req let mut http_req = req
.try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None) .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld",
SendAccessToken::None,
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(

View File

@ -6,8 +6,8 @@ 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, MatrixError, ServerError}, error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError, MatrixError, ServerError},
AuthScheme, EndpointError, IncomingRequest, IncomingResponse, Metadata, OutgoingRequest, AuthScheme, EndpointError, IncomingRequest, IncomingResponse, MatrixVersion, Metadata,
OutgoingResponse, SendAccessToken, OutgoingRequest, OutgoingResponse, SendAccessToken,
}; };
use ruma_identifiers::{RoomAliasId, RoomId}; use ruma_identifiers::{RoomAliasId, RoomId};
use ruma_serde::Outgoing; use ruma_serde::Outgoing;
@ -49,6 +49,8 @@ impl OutgoingRequest for Request {
self, self,
base_url: &str, base_url: &str,
_access_token: SendAccessToken<'_>, _access_token: SendAccessToken<'_>,
// FIXME: properly integrate
_considering_versions: &'_ [MatrixVersion],
) -> Result<http::Request<T>, 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());

View File

@ -1,4 +1,4 @@
use ruma_api::{OutgoingRequest as _, OutgoingResponse as _, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest as _, OutgoingResponse as _, SendAccessToken};
mod get { mod get {
ruma_api::ruma_api! { ruma_api::ruma_api! {
@ -36,7 +36,11 @@ mod post {
fn empty_post_request_http_repr() { fn empty_post_request_http_repr() {
let req = post::Request {}; let req = post::Request {};
let http_req = req let http_req = req
.try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None) .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld",
SendAccessToken::None,
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
// Empty POST requests should contain an empty dictionary as a body... // Empty POST requests should contain an empty dictionary as a body...
@ -46,7 +50,11 @@ fn empty_post_request_http_repr() {
fn empty_get_request_http_repr() { fn empty_get_request_http_repr() {
let req = get::Request {}; let req = get::Request {};
let http_req = req let http_req = req
.try_into_http_request::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None) .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld",
SendAccessToken::None,
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
// ... but GET requests' bodies should be empty. // ... but GET requests' bodies should be empty.

View File

@ -0,0 +1,101 @@
use http::Method;
use matches::assert_matches;
use ruma_api::{
error::IntoHttpError,
select_path,
MatrixVersion::{V1_0, V1_1, V1_2},
Metadata,
};
const BASE: Metadata = Metadata {
description: "",
method: Method::GET,
name: "test_endpoint",
path: "/depr/path",
unstable_path: Some("/unstable/path"),
r0_path: Some("/r0/path"),
stable_path: Some("/stable/path"),
rate_limited: false,
authentication: ruma_api::AuthScheme::None,
added: None,
deprecated: None,
removed: None,
};
const U: &str = "u";
const S: &str = "s";
const R: &str = "r";
// TODO add test that can hook into tracing and verify the deprecation warning is emitted
#[test]
fn select_stable() {
let meta = Metadata { added: Some(V1_1), ..BASE };
let res = select_path(&[V1_0, V1_1], &meta, None, None, Some(format_args!("{}", S)))
.unwrap()
.to_string();
assert_eq!(res, S);
}
#[test]
fn select_unstable() {
let meta = BASE;
let res =
select_path(&[V1_0], &meta, Some(format_args!("{}", U)), None, None).unwrap().to_string();
assert_eq!(res, U);
}
#[test]
fn select_r0() {
let meta = Metadata { added: Some(V1_0), ..BASE };
let res =
select_path(&[V1_0], &meta, None, Some(format_args!("{}", R)), Some(format_args!("{}", S)))
.unwrap()
.to_string();
assert_eq!(res, R);
}
#[test]
fn select_removed_err() {
let meta = Metadata { added: Some(V1_0), deprecated: Some(V1_1), removed: Some(V1_2), ..BASE };
let res = select_path(
&[V1_2],
&meta,
Some(format_args!("{}", U)),
Some(format_args!("{}", R)),
Some(format_args!("{}", S)),
)
.unwrap_err();
assert_matches!(res, IntoHttpError::EndpointRemoved(V1_2));
}
#[test]
fn partially_removed_but_stable() {
let meta = Metadata { added: Some(V1_0), deprecated: Some(V1_1), removed: Some(V1_2), ..BASE };
let res =
select_path(&[V1_1], &meta, None, Some(format_args!("{}", R)), Some(format_args!("{}", S)))
.unwrap()
.to_string();
assert_eq!(res, S);
}
#[test]
fn no_unstable() {
let meta = Metadata { added: Some(V1_1), ..BASE };
let res =
select_path(&[V1_0], &meta, None, Some(format_args!("{}", R)), Some(format_args!("{}", S)))
.unwrap_err();
assert_matches!(res, IntoHttpError::NoUnstablePath);
}

View File

@ -173,6 +173,7 @@ mod tests {
.try_into_http_request::<Vec<u8>>( .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld", "https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"), SendAccessToken::IfRequired("auth_tok"),
&[ruma_api::MatrixVersion::V1_0],
) )
.unwrap(); .unwrap();
let json_body: serde_json::Value = serde_json::from_slice(req.body()).unwrap(); let json_body: serde_json::Value = serde_json::from_slice(req.body()).unwrap();

View File

@ -76,7 +76,7 @@ mod tests {
#[cfg(feature = "client")] #[cfg(feature = "client")]
#[test] #[test]
fn construct_request_from_refs() { fn construct_request_from_refs() {
use ruma_api::{OutgoingRequest as _, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest as _, SendAccessToken};
use ruma_identifiers::server_name; use ruma_identifiers::server_name;
let req = super::Request { let req = super::Request {
@ -87,6 +87,7 @@ mod tests {
.try_into_http_request::<Vec<u8>>( .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld", "https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"), SendAccessToken::IfRequired("auth_tok"),
&[MatrixVersion::V1_0],
) )
.unwrap(); .unwrap();

View File

@ -77,7 +77,7 @@ mod tests {
#[cfg(feature = "client")] #[cfg(feature = "client")]
#[test] #[test]
fn serialize_request() { fn serialize_request() {
use ruma_api::{OutgoingRequest, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest, SendAccessToken};
use ruma_identifiers::user_id; use ruma_identifiers::user_id;
use crate::r0::filter::FilterDefinition; use crate::r0::filter::FilterDefinition;
@ -87,6 +87,7 @@ mod tests {
.try_into_http_request::<Vec<u8>>( .try_into_http_request::<Vec<u8>>(
"https://matrix.org", "https://matrix.org",
SendAccessToken::IfRequired("tok"), SendAccessToken::IfRequired("tok"),
&[MatrixVersion::V1_0]
), ),
Ok(res) if res.body() == b"{}" Ok(res) if res.body() == b"{}"
); );

View File

@ -141,7 +141,7 @@ pub enum Direction {
#[cfg(all(test, feature = "client"))] #[cfg(all(test, feature = "client"))]
mod tests { mod tests {
use js_int::uint; use js_int::uint;
use ruma_api::{OutgoingRequest, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest, SendAccessToken};
use ruma_identifiers::room_id; use ruma_identifiers::room_id;
use super::{Direction, Request}; use super::{Direction, Request};
@ -175,6 +175,7 @@ mod tests {
.try_into_http_request( .try_into_http_request(
"https://homeserver.tld", "https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"), SendAccessToken::IfRequired("auth_tok"),
&[MatrixVersion::V1_0],
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@ -203,6 +204,7 @@ mod tests {
.try_into_http_request::<Vec<u8>>( .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld", "https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"), SendAccessToken::IfRequired("auth_tok"),
&[MatrixVersion::V1_0],
) )
.unwrap(); .unwrap();
assert_eq!("from=token&to=token2&dir=b&limit=0", request.uri().query().unwrap(),); assert_eq!("from=token&to=token2&dir=b&limit=0", request.uri().query().unwrap(),);

View File

@ -317,7 +317,7 @@ mod tests {
#[test] #[test]
#[cfg(feature = "client")] #[cfg(feature = "client")]
fn serialize_login_request_body() { fn serialize_login_request_body() {
use ruma_api::{OutgoingRequest, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest, SendAccessToken};
use ruma_common::thirdparty::Medium; use ruma_common::thirdparty::Medium;
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
@ -329,7 +329,11 @@ mod tests {
device_id: None, device_id: None,
initial_device_display_name: Some("test"), initial_device_display_name: Some("test"),
} }
.try_into_http_request("https://homeserver.tld", SendAccessToken::None) .try_into_http_request(
"https://homeserver.tld",
SendAccessToken::None,
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
let req_body_value: JsonValue = serde_json::from_slice(req.body()).unwrap(); let req_body_value: JsonValue = serde_json::from_slice(req.body()).unwrap();
@ -353,7 +357,11 @@ mod tests {
device_id: None, device_id: None,
initial_device_display_name: Some("test"), initial_device_display_name: Some("test"),
} }
.try_into_http_request("https://homeserver.tld", SendAccessToken::None) .try_into_http_request(
"https://homeserver.tld",
SendAccessToken::None,
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
let req_body_value: JsonValue = serde_json::from_slice(req.body()).unwrap(); let req_body_value: JsonValue = serde_json::from_slice(req.body()).unwrap();

View File

@ -45,14 +45,18 @@ impl Response {
#[cfg(all(test, feature = "client"))] #[cfg(all(test, feature = "client"))]
mod tests { mod tests {
use ruma_api::{OutgoingRequest, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest, SendAccessToken};
use super::Request; use super::Request;
#[test] #[test]
fn serialize_sso_login_request_uri() { fn serialize_sso_login_request_uri() {
let req: http::Request<Vec<u8>> = Request { redirect_url: "https://example.com/sso" } let req: http::Request<Vec<u8>> = Request { redirect_url: "https://example.com/sso" }
.try_into_http_request("https://homeserver.tld", SendAccessToken::None) .try_into_http_request(
"https://homeserver.tld",
SendAccessToken::None,
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(

View File

@ -69,17 +69,29 @@ impl<'a> ruma_api::OutgoingRequest for Request<'a> {
self, self,
base_url: &str, base_url: &str,
access_token: ruma_api::SendAccessToken<'_>, access_token: ruma_api::SendAccessToken<'_>,
considering_versions: &'_ [ruma_api::MatrixVersion],
) -> Result<http::Request<T>, ruma_api::error::IntoHttpError> { ) -> Result<http::Request<T>, ruma_api::error::IntoHttpError> {
use std::borrow::Cow; use std::borrow::Cow;
use http::header; use http::header;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
let room_id_percent = utf8_percent_encode(self.room_id.as_str(), NON_ALPHANUMERIC);
let event_type_percent = utf8_percent_encode(self.event_type.as_str(), NON_ALPHANUMERIC);
let mut url = format!( let mut url = format!(
"{}/_matrix/client/r0/rooms/{}/state/{}", "{}{}",
base_url.strip_suffix('/').unwrap_or(base_url), base_url.strip_suffix('/').unwrap_or(base_url),
utf8_percent_encode(self.room_id.as_str(), NON_ALPHANUMERIC), ruma_api::select_path(
utf8_percent_encode(self.event_type.as_str(), NON_ALPHANUMERIC) considering_versions,
&METADATA,
None,
Some(format_args!(
"/_matrix/client/r0/rooms/{}/state/{}",
room_id_percent, event_type_percent
)),
None,
)?
); );
if !self.state_key.is_empty() { if !self.state_key.is_empty() {

View File

@ -93,17 +93,29 @@ impl<'a> ruma_api::OutgoingRequest for Request<'a> {
self, self,
base_url: &str, base_url: &str,
access_token: ruma_api::SendAccessToken<'_>, access_token: ruma_api::SendAccessToken<'_>,
considering_versions: &'_ [ruma_api::MatrixVersion],
) -> Result<http::Request<T>, ruma_api::error::IntoHttpError> { ) -> Result<http::Request<T>, ruma_api::error::IntoHttpError> {
use std::borrow::Cow; use std::borrow::Cow;
use http::header::{self, HeaderValue}; use http::header::{self, HeaderValue};
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
let room_id_percent = utf8_percent_encode(self.room_id.as_str(), NON_ALPHANUMERIC);
let event_type_percent = utf8_percent_encode(self.event_type, NON_ALPHANUMERIC);
let mut url = format!( let mut url = format!(
"{}/_matrix/client/r0/rooms/{}/state/{}", "{}{}",
base_url.strip_suffix('/').unwrap_or(base_url), base_url.strip_suffix('/').unwrap_or(base_url),
utf8_percent_encode(self.room_id.as_str(), NON_ALPHANUMERIC), ruma_api::select_path(
utf8_percent_encode(self.event_type, NON_ALPHANUMERIC), considering_versions,
&METADATA,
None,
Some(format_args!(
"/_matrix/client/r0/rooms/{}/state/{}",
room_id_percent, event_type_percent
)),
None,
)?
); );
// Last URL segment is optional, that is why this trait impl is not generated. // Last URL segment is optional, that is why this trait impl is not generated.

View File

@ -610,7 +610,7 @@ mod tests {
mod client_tests { mod client_tests {
use std::time::Duration; use std::time::Duration;
use ruma_api::{OutgoingRequest as _, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest as _, SendAccessToken};
use super::{Filter, PresenceState, Request}; use super::{Filter, PresenceState, Request};
@ -623,7 +623,11 @@ mod client_tests {
set_presence: &PresenceState::Offline, set_presence: &PresenceState::Offline,
timeout: Some(Duration::from_millis(30000)), timeout: Some(Duration::from_millis(30000)),
} }
.try_into_http_request("https://homeserver.tld", SendAccessToken::IfRequired("auth_tok")) .try_into_http_request(
"https://homeserver.tld",
SendAccessToken::IfRequired("auth_tok"),
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
let uri = req.uri(); let uri = req.uri();

View File

@ -49,14 +49,18 @@ impl Response {
#[cfg(all(test, feature = "client"))] #[cfg(all(test, feature = "client"))]
mod tests { mod tests {
use ruma_api::{OutgoingRequest as _, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest as _, SendAccessToken};
use super::Request; use super::Request;
#[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::<Vec<u8>>("https://homeserver.tld", SendAccessToken::None) .try_into_http_request::<Vec<u8>>(
"https://homeserver.tld",
SendAccessToken::None,
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(

View File

@ -1,13 +1,17 @@
#![cfg(feature = "client")] #![cfg(feature = "client")]
use http::HeaderMap; use http::HeaderMap;
use ruma_api::{OutgoingRequest as _, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest as _, SendAccessToken};
use ruma_client_api::unversioned::discover_homeserver; use ruma_client_api::unversioned::discover_homeserver;
#[test] #[test]
fn get_request_headers() { fn get_request_headers() {
let req: http::Request<Vec<u8>> = discover_homeserver::Request::new() let req: http::Request<Vec<u8>> = discover_homeserver::Request::new()
.try_into_http_request("https://homeserver.tld", SendAccessToken::None) .try_into_http_request(
"https://homeserver.tld",
SendAccessToken::None,
&[MatrixVersion::V1_0],
)
.unwrap(); .unwrap();
assert_eq!(*req.headers(), HeaderMap::default()); assert_eq!(*req.headers(), HeaderMap::default());

View File

@ -3,6 +3,7 @@ use std::time::Duration;
use assign::assign; use assign::assign;
use async_stream::try_stream; use async_stream::try_stream;
use futures_core::stream::Stream; use futures_core::stream::Stream;
use ruma_api::MatrixVersion;
use ruma_client_api::r0::{ use ruma_client_api::r0::{
account::register::{self, RegistrationKind}, account::register::{self, RegistrationKind},
session::login::{self, LoginInfo}, session::login::{self, LoginInfo},
@ -33,7 +34,7 @@ impl<C: HttpClient> Client<C> {
device_id, device_id,
initial_device_display_name, initial_device_display_name,
} }
)) ), &[MatrixVersion::V1_0])
.await?; .await?;
*self.0.access_token.lock().unwrap() = Some(response.access_token.clone()); *self.0.access_token.lock().unwrap() = Some(response.access_token.clone());
@ -49,7 +50,10 @@ impl<C: HttpClient> Client<C> {
&self, &self,
) -> Result<register::Response, Error<C::Error, ruma_client_api::r0::uiaa::UiaaResponse>> { ) -> Result<register::Response, Error<C::Error, ruma_client_api::r0::uiaa::UiaaResponse>> {
let response = self let response = self
.send_request(assign!(register::Request::new(), { kind: RegistrationKind::Guest })) .send_request(
assign!(register::Request::new(), { kind: RegistrationKind::Guest }),
&[MatrixVersion::V1_0],
)
.await?; .await?;
*self.0.access_token.lock().unwrap() = response.access_token.clone(); *self.0.access_token.lock().unwrap() = response.access_token.clone();
@ -70,7 +74,10 @@ impl<C: HttpClient> Client<C> {
password: &str, password: &str,
) -> Result<register::Response, Error<C::Error, ruma_client_api::r0::uiaa::UiaaResponse>> { ) -> Result<register::Response, Error<C::Error, ruma_client_api::r0::uiaa::UiaaResponse>> {
let response = self let response = self
.send_request(assign!(register::Request::new(), { username, password: Some(password) })) .send_request(
assign!(register::Request::new(), { username, password: Some(password)}),
&[MatrixVersion::V1_0],
)
.await?; .await?;
*self.0.access_token.lock().unwrap() = response.access_token.clone(); *self.0.access_token.lock().unwrap() = response.access_token.clone();
@ -120,7 +127,7 @@ impl<C: HttpClient> Client<C> {
since: Some(&since), since: Some(&since),
set_presence, set_presence,
timeout, timeout,
})) }), &[MatrixVersion::V1_0])
.await?; .await?;
since = response.next_batch.clone(); since = response.next_batch.clone();

View File

@ -5,7 +5,7 @@ use std::{future::Future, pin::Pin};
use async_trait::async_trait; use async_trait::async_trait;
use bytes::BufMut; use bytes::BufMut;
use ruma_api::{OutgoingRequest, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest, SendAccessToken};
use ruma_identifiers::UserId; use ruma_identifiers::UserId;
use crate::{add_user_id_to_query, ResponseError, ResponseResult}; use crate::{add_user_id_to_query, ResponseError, ResponseResult};
@ -64,9 +64,16 @@ pub trait HttpClientExt: HttpClient {
&'a self, &'a self,
homeserver_url: &str, homeserver_url: &str,
access_token: SendAccessToken<'_>, access_token: SendAccessToken<'_>,
for_versions: &[MatrixVersion],
request: R, request: R,
) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>> { ) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>> {
self.send_customized_matrix_request(homeserver_url, access_token, request, |_| Ok(())) self.send_customized_matrix_request(
homeserver_url,
access_token,
for_versions,
request,
|_| Ok(()),
)
} }
/// Turn a strongly-typed matrix request into an `http::Request`, customize it and send it to /// Turn a strongly-typed matrix request into an `http::Request`, customize it and send it to
@ -76,6 +83,7 @@ pub trait HttpClientExt: HttpClient {
&'a self, &'a self,
homeserver_url: &str, homeserver_url: &str,
access_token: SendAccessToken<'_>, access_token: SendAccessToken<'_>,
for_versions: &[MatrixVersion],
request: R, request: R,
customize: F, customize: F,
) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>> ) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>>
@ -87,6 +95,7 @@ pub trait HttpClientExt: HttpClient {
self, self,
homeserver_url, homeserver_url,
access_token, access_token,
for_versions,
request, request,
customize, customize,
)) ))
@ -101,12 +110,14 @@ pub trait HttpClientExt: HttpClient {
&'a self, &'a self,
homeserver_url: &str, homeserver_url: &str,
access_token: SendAccessToken<'_>, access_token: SendAccessToken<'_>,
for_versions: &[MatrixVersion],
user_id: &'a UserId, user_id: &'a UserId,
request: R, request: R,
) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>> { ) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>> {
self.send_customized_matrix_request( self.send_customized_matrix_request(
homeserver_url, homeserver_url,
access_token, access_token,
for_versions,
request, request,
add_user_id_to_query::<Self, R>(user_id), add_user_id_to_query::<Self, R>(user_id),
) )

View File

@ -58,10 +58,11 @@
//! //!
//! use ruma_client_api::r0::alias::get_alias; //! use ruma_client_api::r0::alias::get_alias;
//! use ruma_identifiers::{room_alias_id, room_id}; //! use ruma_identifiers::{room_alias_id, room_id};
//! use ruma_api::MatrixVersion;
//! //!
//! async { //! async {
//! let response = client //! let response = client
//! .send_request(get_alias::Request::new(room_alias_id!("#example_room:example.com"))) //! .send_request(get_alias::Request::new(room_alias_id!("#example_room:example.com")), &[MatrixVersion::V1_0])
//! .await?; //! .await?;
//! //!
//! assert_eq!(response.room_id, room_id!("!n8f893n9:example.com")); //! assert_eq!(response.room_id, room_id!("!n8f893n9:example.com"));
@ -97,7 +98,7 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use ruma_api::{OutgoingRequest, SendAccessToken}; use ruma_api::{MatrixVersion, OutgoingRequest, SendAccessToken};
use ruma_identifiers::UserId; use ruma_identifiers::UserId;
// "Undo" rename from `Cargo.toml` that only serves to make crate names available as a Cargo // "Undo" rename from `Cargo.toml` that only serves to make crate names available as a Cargo
@ -179,14 +180,19 @@ impl<C: DefaultConstructibleHttpClient> Client<C> {
impl<C: HttpClient> Client<C> { impl<C: HttpClient> Client<C> {
/// Makes a request to a Matrix API endpoint. /// Makes a request to a Matrix API endpoint.
pub async fn send_request<R: OutgoingRequest>(&self, request: R) -> ResponseResult<C, R> { pub async fn send_request<R: OutgoingRequest>(
self.send_customized_request(request, |_| Ok(())).await &self,
request: R,
for_versions: &[MatrixVersion],
) -> ResponseResult<C, R> {
self.send_customized_request(request, for_versions, |_| Ok(())).await
} }
/// Makes a request to a Matrix API endpoint including additional URL parameters. /// Makes a request to a Matrix API endpoint including additional URL parameters.
pub async fn send_customized_request<R, F>( pub async fn send_customized_request<R, F>(
&self, &self,
request: R, request: R,
for_versions: &[MatrixVersion],
customize: F, customize: F,
) -> ResponseResult<C, R> ) -> ResponseResult<C, R>
where where
@ -203,6 +209,7 @@ impl<C: HttpClient> Client<C> {
&self.0.http_client, &self.0.http_client,
&self.0.homeserver_url, &self.0.homeserver_url,
send_access_token, send_access_token,
for_versions,
request, request,
customize, customize,
) )
@ -217,8 +224,10 @@ impl<C: HttpClient> Client<C> {
&self, &self,
user_id: &UserId, user_id: &UserId,
request: R, request: R,
for_versions: &[MatrixVersion],
) -> ResponseResult<C, R> { ) -> ResponseResult<C, R> {
self.send_customized_request(request, add_user_id_to_query::<C, R>(user_id)).await self.send_customized_request(request, for_versions, add_user_id_to_query::<C, R>(user_id))
.await
} }
} }
@ -226,6 +235,7 @@ fn send_customized_request<'a, C, R, F>(
http_client: &'a C, http_client: &'a C,
homeserver_url: &str, homeserver_url: &str,
send_access_token: SendAccessToken<'_>, send_access_token: SendAccessToken<'_>,
for_versions: &[MatrixVersion],
request: R, request: R,
customize: F, customize: F,
) -> impl Future<Output = ResponseResult<C, R>> + Send + 'a ) -> impl Future<Output = ResponseResult<C, R>> + Send + 'a
@ -235,7 +245,7 @@ where
F: FnOnce(&mut http::Request<C::RequestBody>) -> Result<(), ResponseError<C, R>>, F: FnOnce(&mut http::Request<C::RequestBody>) -> Result<(), ResponseError<C, R>>,
{ {
let http_req = request let http_req = request
.try_into_http_request(homeserver_url, send_access_token) .try_into_http_request(homeserver_url, send_access_token, for_versions)
.map_err(ResponseError::<C, R>::from) .map_err(ResponseError::<C, R>::from)
.and_then(|mut req| { .and_then(|mut req| {
customize(&mut req)?; customize(&mut req)?;

View File

@ -5,6 +5,7 @@ use ruma::{
events::room::message::RoomMessageEventContent, events::room::message::RoomMessageEventContent,
RoomAliasId, RoomAliasId,
}; };
use ruma_api::MatrixVersion;
use ruma_identifiers::TransactionId; use ruma_identifiers::TransactionId;
type MatrixClient = ruma_client::Client<ruma_client::http_client::HyperNativeTls>; type MatrixClient = ruma_client::Client<ruma_client::http_client::HyperNativeTls>;
@ -18,14 +19,20 @@ async fn hello_world(
let client = MatrixClient::new(homeserver_url, None); let client = MatrixClient::new(homeserver_url, None);
client.log_in(username, password, None, Some("ruma-example-client")).await?; client.log_in(username, password, None, Some("ruma-example-client")).await?;
let room_id = client.send_request(get_alias::Request::new(room_alias)).await?.room_id; let room_id = client
client.send_request(join_room_by_id::Request::new(&room_id)).await?; .send_request(get_alias::Request::new(room_alias), &[MatrixVersion::V1_0])
.await?
.room_id;
client.send_request(join_room_by_id::Request::new(&room_id), &[MatrixVersion::V1_0]).await?;
client client
.send_request(send_message_event::Request::new( .send_request(
send_message_event::Request::new(
&room_id, &room_id,
&TransactionId::new(), &TransactionId::new(),
&RoomMessageEventContent::text_plain("Hello World!"), &RoomMessageEventContent::text_plain("Hello World!"),
)?) )?,
&[MatrixVersion::V1_0],
)
.await?; .await?;
Ok(()) Ok(())

View File

@ -24,9 +24,12 @@ async fn log_messages(
let filter = FilterDefinition::ignore_all().into(); let filter = FilterDefinition::ignore_all().into();
let initial_sync_response = client let initial_sync_response = client
.send_request(assign!(sync_events::Request::new(), { .send_request(
assign!(sync_events::Request::new(), {
filter: Some(&filter), filter: Some(&filter),
})) }),
&[ruma_api::MatrixVersion::V1_0],
)
.await?; .await?;
let mut sync_stream = Box::pin(client.sync( let mut sync_stream = Box::pin(client.sync(

View File

@ -1,10 +1,13 @@
use std::{convert::TryInto, error::Error, io, process::exit, time::Duration}; use std::{convert::TryInto, error::Error, io, process::exit, time::Duration};
use ruma::{ use ruma::{
api::client::r0::{ api::{
client::r0::{
filter::FilterDefinition, membership::join_room_by_id, message::send_message_event, filter::FilterDefinition, membership::join_room_by_id, message::send_message_event,
sync::sync_events, sync::sync_events,
}, },
MatrixVersion,
},
assign, client, assign, client,
events::{ events::{
room::message::{MessageType, RoomMessageEventContent}, room::message::{MessageType, RoomMessageEventContent},
@ -59,9 +62,12 @@ async fn run() -> Result<(), Box<dyn Error>> {
let filter = FilterDefinition::ignore_all().into(); let filter = FilterDefinition::ignore_all().into();
let initial_sync_response = matrix_client let initial_sync_response = matrix_client
.send_request(assign!(sync_events::Request::new(), { .send_request(
assign!(sync_events::Request::new(), {
filter: Some(&filter), filter: Some(&filter),
})) }),
&[MatrixVersion::V1_0],
)
.await?; .await?;
let user_id = &config.username; let user_id = &config.username;
let not_senders = &[user_id.clone()]; let not_senders = &[user_id.clone()];
@ -152,7 +158,7 @@ async fn handle_messages(
let txn_id = TransactionId::new(); let txn_id = TransactionId::new();
let req = send_message_event::Request::new(room_id, &txn_id, &joke_content)?; let req = send_message_event::Request::new(room_id, &txn_id, &joke_content)?;
// Do nothing if we can't send the message. // Do nothing if we can't send the message.
let _ = matrix_client.send_request(req).await; let _ = matrix_client.send_request(req, &[MatrixVersion::V1_0]).await;
} }
} }
} }
@ -165,14 +171,16 @@ async fn handle_invitations(
room_id: &RoomId, room_id: &RoomId,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
println!("invited to {}", &room_id); println!("invited to {}", &room_id);
matrix_client.send_request(join_room_by_id::Request::new(room_id)).await?; matrix_client
.send_request(join_room_by_id::Request::new(room_id), &[MatrixVersion::V1_0])
.await?;
let greeting = "Hello! My name is Mr. Bot! I like to tell jokes. Like this one: "; let greeting = "Hello! My name is Mr. Bot! I like to tell jokes. Like this one: ";
let joke = get_joke(http_client).await.unwrap_or_else(|_| "err... never mind.".to_owned()); let joke = get_joke(http_client).await.unwrap_or_else(|_| "err... never mind.".to_owned());
let content = RoomMessageEventContent::text_plain(format!("{}\n{}", greeting, joke)); let content = RoomMessageEventContent::text_plain(format!("{}\n{}", greeting, joke));
let txn_id = TransactionId::new(); let txn_id = TransactionId::new();
let message = send_message_event::Request::new(room_id, &txn_id, &content)?; let message = send_message_event::Request::new(room_id, &txn_id, &content)?;
matrix_client.send_request(message).await?; matrix_client.send_request(message, &[MatrixVersion::V1_0]).await?;
Ok(()) Ok(())
} }