diff --git a/ruma-client/examples/hello_world.rs b/ruma-client/examples/hello_world.rs index 8e6543e6..94e10005 100644 --- a/ruma-client/examples/hello_world.rs +++ b/ruma-client/examples/hello_world.rs @@ -1,6 +1,5 @@ use std::{convert::TryFrom, env, process::exit}; -use http::Uri; use ruma::{ api::client::r0::{alias::get_alias, membership::join_room_by_id, message::send_message_event}, events::{room::message::MessageEventContent, AnyMessageEventContent}, @@ -10,7 +9,7 @@ use ruma::{ type MatrixClient = ruma_client::Client; async fn hello_world( - homeserver_url: Uri, + homeserver_url: String, username: &str, password: &str, room_alias: &RoomAliasId, @@ -18,10 +17,10 @@ async fn hello_world( let client = MatrixClient::new(homeserver_url, None); client.log_in(username, password, None, Some("ruma-example-client")).await?; - let room_id = client.request(get_alias::Request::new(room_alias)).await?.room_id; - client.request(join_room_by_id::Request::new(&room_id)).await?; + let room_id = client.send_request(get_alias::Request::new(room_alias)).await?.room_id; + client.send_request(join_room_by_id::Request::new(&room_id)).await?; client - .request(send_message_event::Request::new( + .send_request(send_message_event::Request::new( &room_id, "1", &AnyMessageEventContent::RoomMessage(MessageEventContent::text_plain("Hello World!")), @@ -45,11 +44,5 @@ async fn main() -> anyhow::Result<()> { } }; - hello_world( - homeserver_url.parse()?, - &username, - &password, - &RoomAliasId::try_from(room.as_str())?, - ) - .await + hello_world(homeserver_url, &username, &password, &RoomAliasId::try_from(room.as_str())?).await } diff --git a/ruma-client/examples/message_log.rs b/ruma-client/examples/message_log.rs index 0f5c507d..551f9e17 100644 --- a/ruma-client/examples/message_log.rs +++ b/ruma-client/examples/message_log.rs @@ -1,7 +1,6 @@ use std::{env, process::exit, time::Duration}; use assign::assign; -use http::Uri; use ruma::{ api::client::r0::{filter::FilterDefinition, sync::sync_events}, events::{ @@ -14,14 +13,18 @@ use tokio_stream::StreamExt as _; type MatrixClient = ruma_client::Client; -async fn log_messages(homeserver_url: Uri, username: &str, password: &str) -> anyhow::Result<()> { +async fn log_messages( + homeserver_url: String, + username: &str, + password: &str, +) -> anyhow::Result<()> { let client = MatrixClient::new(homeserver_url, None); client.log_in(username, password, None, None).await?; let filter = FilterDefinition::ignore_all().into(); let initial_sync_response = client - .request(assign!(sync_events::Request::new(), { + .send_request(assign!(sync_events::Request::new(), { filter: Some(&filter), })) .await?; @@ -76,6 +79,5 @@ async fn main() -> anyhow::Result<()> { } }; - let server = homeserver_url.parse()?; - log_messages(server, &username, &password).await + log_messages(homeserver_url, &username, &password).await } diff --git a/ruma-client/src/client_api.rs b/ruma-client/src/client_api.rs index 5a2f5bd5..033ee21a 100644 --- a/ruma-client/src/client_api.rs +++ b/ruma-client/src/client_api.rs @@ -27,7 +27,7 @@ impl Client { initial_device_display_name: Option<&str>, ) -> Result> { let response = self - .request(assign!( + .send_request(assign!( login::Request::new( LoginInfo::Password { identifier: UserIdentifier::MatrixId(user), password } ), { @@ -50,7 +50,7 @@ impl Client { &self, ) -> Result> { let response = self - .request(assign!(register::Request::new(), { kind: RegistrationKind::Guest })) + .send_request(assign!(register::Request::new(), { kind: RegistrationKind::Guest })) .await?; *self.0.access_token.lock().unwrap() = response.access_token.clone(); @@ -71,7 +71,7 @@ impl Client { password: &str, ) -> Result> { let response = self - .request(assign!(register::Request::new(), { username, password: Some(password) })) + .send_request(assign!(register::Request::new(), { username, password: Some(password) })) .await?; *self.0.access_token.lock().unwrap() = response.access_token.clone(); @@ -116,7 +116,7 @@ impl Client { try_stream! { loop { let response = self - .request(assign!(sync_events::Request::new(), { + .send_request(assign!(sync_events::Request::new(), { filter, since: Some(&since), set_presence, diff --git a/ruma-client/src/http_client.rs b/ruma-client/src/http_client.rs index 17ed09ce..3973e89b 100644 --- a/ruma-client/src/http_client.rs +++ b/ruma-client/src/http_client.rs @@ -1,8 +1,13 @@ //! This module contains an abstraction for HTTP clients as well as friendly-named re-exports of //! client types that implement this trait. +use std::{collections::BTreeMap, future::Future, pin::Pin}; + use async_trait::async_trait; use bytes::BufMut; +use ruma_api::{OutgoingRequest, SendAccessToken}; + +use crate::ResponseResult; #[cfg(feature = "hyper")] mod hyper; @@ -16,9 +21,9 @@ pub use self::hyper::HyperRustls; /// An HTTP client that can be used to send requests to a Matrix homeserver. #[async_trait] -pub trait HttpClient { +pub trait HttpClient: Sync { /// The type to use for `try_into_http_request`. - type RequestBody: Default + BufMut; + type RequestBody: Default + BufMut + Send; /// The type to use for `try_from_http_response`. type ResponseBody: AsRef<[u8]>; @@ -38,3 +43,46 @@ pub trait DefaultConstructibleHttpClient: HttpClient { /// Creates a new HTTP client with default configuration. fn default() -> Self; } + +/// Convenience functionality on top of `HttpClient`. +/// +/// If you want to build your own matrix client type instead of using `ruma_client::Client`, this +/// trait should make that relatively easy. +pub trait HttpClientExt: HttpClient { + /// Send a strongly-typed matrix request to get back a strongly-typed response. + // TODO: `R: 'a` bound should not be needed + fn send_request<'a, R: OutgoingRequest + 'a>( + &'a self, + homeserver_url: &str, + access_token: SendAccessToken<'_>, + request: R, + ) -> Pin> + 'a>> { + Box::pin(crate::send_request_with_url_params( + self, + homeserver_url, + access_token, + None, + request, + )) + } + + /// Send a strongly-typed matrix request to get back a strongly-typed response. + fn send_request_with_url_params<'a, R: OutgoingRequest + 'a>( + &'a self, + homeserver_url: &str, + access_token: SendAccessToken<'_>, + extra_params: BTreeMap, + request: R, + ) -> Pin> + 'a>> { + Box::pin(crate::send_request_with_url_params( + self, + homeserver_url, + access_token, + Some(extra_params), + request, + )) + } +} + +#[async_trait] +impl HttpClientExt for T {} diff --git a/ruma-client/src/lib.rs b/ruma-client/src/lib.rs index 80afe749..f83afc0b 100644 --- a/ruma-client/src/lib.rs +++ b/ruma-client/src/lib.rs @@ -75,12 +75,13 @@ use std::{ collections::BTreeMap, + future::Future, sync::{Arc, Mutex}, }; use assign::assign; use http::uri::Uri; -use ruma_api::{AuthScheme, OutgoingRequest, SendAccessToken}; +use ruma_api::{OutgoingRequest, SendAccessToken}; use ruma_serde::urlencoded; // "Undo" rename from `Cargo.toml` that only serves to make `hyper-rustls` available as a Cargo @@ -95,9 +96,15 @@ pub mod http_client; pub use self::{ error::Error, - http_client::{DefaultConstructibleHttpClient, HttpClient}, + http_client::{DefaultConstructibleHttpClient, HttpClient, HttpClientExt}, }; +/// The result of sending the request `R` with the http client `C`. +pub type ResponseResult = Result< + ::IncomingResponse, + Error<::Error, ::EndpointError>, +>; + /// A client for the Matrix client-server API. #[derive(Clone, Debug)] pub struct Client(Arc>); @@ -106,7 +113,7 @@ pub struct Client(Arc>); #[derive(Debug)] struct ClientData { /// The URL of the homeserver to connect to. - homeserver_url: Uri, + homeserver_url: String, /// The underlying HTTP client. http_client: C, @@ -121,7 +128,7 @@ impl Client { /// This allows the user to configure the details of HTTP as desired. pub fn with_http_client( http_client: C, - homeserver_url: Uri, + homeserver_url: String, access_token: Option, ) -> Self { Self(Arc::new(ClientData { @@ -141,7 +148,7 @@ impl Client { impl Client { /// Creates a new client based on a default-constructed hyper HTTP client. - pub fn new(homeserver_url: Uri, access_token: Option) -> Self { + pub fn new(homeserver_url: String, access_token: Option) -> Self { Self(Arc::new(ClientData { homeserver_url, http_client: DefaultConstructibleHttpClient::default(), @@ -152,48 +159,81 @@ impl Client { impl Client { /// Makes a request to a Matrix API endpoint. - pub async fn request( + pub async fn send_request( &self, - request: Request, - ) -> Result> { - self.request_with_url_params(request, None).await + request: R, + ) -> Result> { + let access_token = self.access_token(); + let send_access_token = match access_token.as_deref() { + Some(at) => SendAccessToken::IfRequired(at), + None => SendAccessToken::None, + }; + + send_request_with_url_params( + &self.0.http_client, + &self.0.homeserver_url, + send_access_token, + None, + request, + ) + .await } /// Makes a request to a Matrix API endpoint including additional URL parameters. - pub async fn request_with_url_params( + pub async fn send_request_with_url_params( &self, - request: Request, - extra_params: Option>, - ) -> Result> { - let client = self.0.clone(); - let mut http_request = { - let lock; - let access_token = if Request::METADATA.authentication == AuthScheme::AccessToken { - lock = client.access_token.lock().unwrap(); - if let Some(access_token) = &*lock { - SendAccessToken::IfRequired(access_token.as_str()) - } else { - return Err(Error::AuthenticationRequired); - } - } else { - SendAccessToken::None + extra_params: BTreeMap, + request: R, + ) -> Result> { + let access_token = self.access_token(); + let send_access_token = match access_token.as_deref() { + Some(at) => SendAccessToken::IfRequired(at), + None => SendAccessToken::None, + }; + + send_request_with_url_params( + &self.0.http_client, + &self.0.homeserver_url, + send_access_token, + Some(extra_params), + request, + ) + .await + } +} + +fn send_request_with_url_params<'a, C, Request>( + http_client: &'a C, + homeserver_url: &str, + send_access_token: SendAccessToken<'_>, + extra_params: Option>, + request: Request, +) -> impl Future>> + + Send + + 'a +where + C: HttpClient + ?Sized, + Request: OutgoingRequest, +{ + let res = request.try_into_http_request(homeserver_url, send_access_token); + + async move { + let mut http_request = res?; + + if let Some(extra_params) = extra_params { + let extra_params = urlencoded::to_string(extra_params).unwrap(); + let uri = http_request.uri_mut(); + let new_path_and_query = match uri.query() { + Some(params) => format!("{}?{}&{}", uri.path(), params, extra_params), + None => format!("{}?{}", uri.path(), extra_params), }; - - request.try_into_http_request(&client.homeserver_url.to_string(), access_token)? - }; - - let extra_params = urlencoded::to_string(extra_params).unwrap(); - let uri = http_request.uri_mut(); - let new_path_and_query = match uri.query() { - Some(params) => format!("{}?{}&{}", uri.path(), params, extra_params), - None => format!("{}?{}", uri.path(), extra_params), - }; - *uri = Uri::from_parts(assign!(uri.clone().into_parts(), { - path_and_query: Some(new_path_and_query.parse()?), - }))?; + *uri = Uri::from_parts(assign!(uri.clone().into_parts(), { + path_and_query: Some(new_path_and_query.parse()?), + }))?; + } let http_response = - client.http_client.send_http_request(http_request).await.map_err(Error::Response)?; + http_client.send_http_request(http_request).await.map_err(Error::Response)?; Ok(ruma_api::IncomingResponse::try_from_http_response(http_response)?) } }