Skip to content

Commit a2a48f5

Browse files
committed
Refactor Client to support AppserviceUserIdentity with improved access token handling
1 parent 1caeac2 commit a2a48f5

File tree

9 files changed

+177
-121
lines changed

9 files changed

+177
-121
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: CI
33
env:
44
CARGO_TERM_COLOR: always
55
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
6-
NIGHTLY: nightly-2025-11-06
6+
NIGHTLY: nightly-2025-08-08
77

88
on:
99
push:
@@ -17,7 +17,7 @@ jobs:
1717
runs-on: ubuntu-latest
1818
steps:
1919
- name: Checkout repo
20-
uses: actions/checkout@v6
20+
uses: actions/checkout@v4
2121

2222
- name: Install rust nightly toolchain
2323
uses: dtolnay/rust-toolchain@master
@@ -27,7 +27,7 @@ jobs:
2727
- uses: Swatinem/rust-cache@v2
2828

2929
- name: Check spelling
30-
uses: crate-ci/typos@v1.39.2
30+
uses: crate-ci/typos@v1.36.0
3131

3232
- name: Install cargo-sort
3333
uses: taiki-e/cache-cargo-install-action@v2
@@ -44,19 +44,19 @@ jobs:
4444
runs-on: ubuntu-latest
4545
steps:
4646
- name: Checkout repo
47-
uses: actions/checkout@v6
47+
uses: actions/checkout@v4
4848

4949
- name: Install MSRV toolchain
5050
uses: dtolnay/rust-toolchain@master
5151
with:
52-
toolchain: "1.88"
52+
toolchain: "1.82"
5353

5454
- uses: Swatinem/rust-cache@v2
5555
with:
5656
# A stable compiler update should automatically not reuse old caches.
5757
# Add the MSRV as a stable cache key too so bumping it also gets us a
5858
# fresh cache.
59-
shared-key: msrv1.88
59+
shared-key: msrv1.82
6060

6161
- name: Run checks
6262
run: cargo check --all-features
@@ -78,7 +78,7 @@ jobs:
7878

7979
steps:
8080
- name: Checkout repo
81-
uses: actions/checkout@v6
81+
uses: actions/checkout@v4
8282

8383
- name: Install rust stable toolchain
8484
uses: dtolnay/rust-toolchain@stable
@@ -102,16 +102,16 @@ jobs:
102102
cmd: check --all-features
103103

104104
- name: Clippy Default Features
105-
cmd: clippy --all-targets -- -D warnings
105+
cmd: clippy
106106
components: clippy
107107

108108
- name: Clippy All Features
109-
cmd: clippy --all-features --all-targets -- -D warnings
109+
cmd: clippy --all-features
110110
components: clippy
111111

112112
steps:
113113
- name: Checkout repo
114-
uses: actions/checkout@v6
114+
uses: actions/checkout@v4
115115

116116
- name: Install rust nightly toolchain
117117
uses: dtolnay/rust-toolchain@master

.github/workflows/deps.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ jobs:
1616
runs-on: ubuntu-latest
1717

1818
steps:
19-
- uses: actions/checkout@v6
19+
- uses: actions/checkout@v4
2020
- uses: EmbarkStudios/cargo-deny-action@v2

CHANGELOG.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# [unreleased]
22

3+
Improvements:
4+
5+
- `ClientBuilder` now has `token_mode()` which takes a `TokenMode` for correlation to `SendAccessToken` behavior.
6+
- Add `SupportedPathBuilder` trait to enable supporting both `VersionHistory` and `SinglePath` path builders in client request methods.
7+
- Add `send_matrix_request_as()` method to `HttpClientExt` trait for application service requests.
8+
39
Breaking changes:
410

5-
- Upgrade ruma to 0.14.0.
6-
- The `send_request`, `send_request_as` and `send_customized_request` of
7-
`Client` now have stricter bounds for the request. These bounds are
8-
compatible with all requests from ruma-client-api and ruma-appservice-api.
9-
- `HttpRequest::RequestBuilder` has an extra `AsRef<[u8]>` bound.
10-
- Bump MSRV to 1.88
11-
12-
Improvements:
11+
- `send_request` and `send_request_as` now require `R::PathBuilder: SupportedPathBuilder` bound.
12+
- `send_request_as` now takes `AppserviceUserIdentity` instead of `&UserId` for the identity parameter.
13+
- Remove `send_customized_request` from `Client`
14+
- Remove `send_customized_matrix_request()` method from `HttpClientExt` trait.
15+
- Add `C::RequestBody: AsRef<[u8]>` bound to various `Client` and `HttpClientExt` methods.
1316

14-
- `ClientBuilder` now has `token_mode()` which takes a `TokenMode` for
15-
correlation to `SendAccessToken` behavior.
16-
1717
# 0.16.0
1818

1919
Breaking changes:

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,4 @@ wildcard_imports = "warn"
8686
# Not that good of a lint
8787
new_without_default = "allow"
8888
# Disabled temporarily because it triggers false positives for types with generics.
89-
arc_with_non_send_sync = "allow"
89+
arc_with_non_send_sync = "allow"

src/client.rs

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use assign::assign;
88
use async_stream::try_stream;
99
use futures_core::stream::Stream;
1010
use ruma::{
11-
DeviceId, UserId,
11+
DeviceId,
1212
api::{
13-
OutgoingRequest, SupportedVersions,
13+
AppserviceUserIdentity, OutgoingRequest, OutgoingRequestAppserviceExt, SupportedVersions,
1414
auth_scheme::{AuthScheme, SendAccessToken},
1515
client::{
1616
account::register::{self, RegistrationKind},
@@ -24,7 +24,8 @@ use ruma::{
2424
};
2525

2626
use crate::{
27-
Error, HttpClient, ResponseError, ResponseResult, add_user_id_to_query, send_customized_request,
27+
Error, HttpClient, ResponseError, ResponseResult, send_customized_request, send_request,
28+
send_request_as,
2829
};
2930

3031
mod builder;
@@ -83,6 +84,18 @@ impl<C> Client<C> {
8384
pub fn access_token(&self) -> Option<String> {
8485
self.0.access_token.lock().expect("session mutex was poisoned").clone()
8586
}
87+
88+
/// Get the `SendAccessToken` according to the client's `TokenMode`.
89+
fn send_access_token<'a>(&self, access_token: &'a Option<String>) -> SendAccessToken<'a> {
90+
let token_mode = self.0.token_mode;
91+
92+
match (token_mode, access_token.as_deref()) {
93+
(TokenMode::AppService, Some(at)) => SendAccessToken::Appservice(at),
94+
(TokenMode::SendIfRequired, Some(at)) => SendAccessToken::IfRequired(at),
95+
(TokenMode::SendAlways, Some(at)) => SendAccessToken::Always(at),
96+
(_, None) => SendAccessToken::None,
97+
}
98+
}
8699
}
87100

88101
impl<C: HttpClient> Client<C> {
@@ -93,7 +106,16 @@ impl<C: HttpClient> Client<C> {
93106
for<'a> R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
94107
R::PathBuilder: SupportedPathBuilder,
95108
{
96-
self.send_customized_request(request, |_| Ok(())).await
109+
let access_token = self.access_token();
110+
111+
send_request(
112+
&self.0.http_client,
113+
&self.0.homeserver_url,
114+
self.send_access_token(&access_token),
115+
R::PathBuilder::get_path_builder_input(self),
116+
request,
117+
)
118+
.await
97119
}
98120

99121
/// Makes a request to a Matrix API endpoint including additional URL parameters.
@@ -108,20 +130,12 @@ impl<C: HttpClient> Client<C> {
108130
R::PathBuilder: SupportedPathBuilder,
109131
F: FnOnce(&mut http::Request<C::RequestBody>) -> Result<(), ResponseError<C, R>>,
110132
{
111-
let token_mode = self.0.token_mode;
112133
let access_token = self.access_token();
113134

114-
let send_access_token = match (token_mode, access_token.as_deref()) {
115-
(TokenMode::AppService, Some(at)) => SendAccessToken::Appservice(at),
116-
(TokenMode::SendIfRequired, Some(at)) => SendAccessToken::IfRequired(at),
117-
(TokenMode::SendAlways, Some(at)) => SendAccessToken::Always(at),
118-
(_, None) => SendAccessToken::None,
119-
};
120-
121135
send_customized_request(
122136
&self.0.http_client,
123137
&self.0.homeserver_url,
124-
send_access_token,
138+
self.send_access_token(&access_token),
125139
R::PathBuilder::get_path_builder_input(self),
126140
request,
127141
customize,
@@ -133,13 +147,27 @@ impl<C: HttpClient> Client<C> {
133147
///
134148
/// This method is meant to be used by application services when interacting with the
135149
/// client-server API.
136-
pub async fn send_request_as<R>(&self, user_id: &UserId, request: R) -> ResponseResult<C, R>
150+
pub async fn send_request_as<R>(
151+
&self,
152+
identity: AppserviceUserIdentity<'_>,
153+
request: R,
154+
) -> ResponseResult<C, R>
137155
where
138-
R: OutgoingRequest,
156+
R: OutgoingRequestAppserviceExt,
139157
for<'a> R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
140158
R::PathBuilder: SupportedPathBuilder,
141159
{
142-
self.send_customized_request(request, add_user_id_to_query::<C, R>(user_id)).await
160+
let access_token = self.access_token();
161+
162+
send_request_as(
163+
&self.0.http_client,
164+
&self.0.homeserver_url,
165+
self.send_access_token(&access_token),
166+
identity,
167+
R::PathBuilder::get_path_builder_input(self),
168+
request,
169+
)
170+
.await
143171
}
144172

145173
/// Log in with a username and password.

src/client/builder.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::sync::{Arc, Mutex};
22

33
use ruma::api::{
4-
SupportedVersions, auth_scheme::SendAccessToken, client::discovery::get_supported_versions,
4+
auth_scheme::SendAccessToken, client::discovery::get_supported_versions, SupportedVersions,
55
};
66

77
use super::{Client, ClientData, TokenMode};
@@ -63,6 +63,7 @@ impl ClientBuilder {
6363
pub async fn build<C>(self) -> Result<Client<C>, Error<C::Error, ruma::api::client::Error>>
6464
where
6565
C: DefaultConstructibleHttpClient,
66+
C::RequestBody: AsRef<[u8]>,
6667
{
6768
self.http_client(C::default()).await
6869
}
@@ -78,6 +79,7 @@ impl ClientBuilder {
7879
) -> Result<Client<C>, Error<C::Error, ruma::api::client::Error>>
7980
where
8081
C: HttpClient,
82+
C::RequestBody: AsRef<[u8]>,
8183
{
8284
let homeserver_url = self
8385
.homeserver_url

src/error.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,7 @@ impl<E: Display, F: Display> Display for Error<E, F> {
4040
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
4141
match self {
4242
Self::AuthenticationRequired => {
43-
write!(
44-
f,
45-
"The queried endpoint requires authentication but was called with an anonymous client."
46-
)
43+
write!(f, "The queried endpoint requires authentication but was called with an anonymous client.")
4744
}
4845
Self::IntoHttp(err) => write!(f, "HTTP request construction failed: {err}"),
4946
Self::Url(err) => write!(f, "Invalid URL: {err}"),

src/http_client.rs

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@
44
use std::{future::Future, pin::Pin};
55

66
use bytes::BufMut;
7-
use ruma::{
8-
UserId,
9-
api::{
10-
OutgoingRequest,
11-
auth_scheme::{AuthScheme, SendAccessToken},
12-
path_builder::PathBuilder,
13-
},
7+
use ruma::api::{
8+
AppserviceUserIdentity, OutgoingRequest,
9+
auth_scheme::{AuthScheme, SendAccessToken},
10+
path_builder::PathBuilder,
1411
};
1512

16-
use crate::{ResponseError, ResponseResult, add_user_id_to_query};
13+
use crate::{ResponseError, ResponseResult};
1714

1815
#[cfg(feature = "hyper")]
1916
mod hyper;
@@ -59,25 +56,19 @@ pub trait DefaultConstructibleHttpClient: HttpClient {
5956
/// trait should make that relatively easy.
6057
pub trait HttpClientExt: HttpClient {
6158
/// Send a strongly-typed matrix request to get back a strongly-typed response.
62-
// TODO: `R: 'a` bound should not be needed
6359
fn send_matrix_request<'a, R>(
6460
&'a self,
6561
homeserver_url: &str,
66-
access_token: SendAccessToken<'a>,
67-
path_builder_input: <R::PathBuilder as PathBuilder>::Input<'_>,
62+
access_token: SendAccessToken<'_>,
63+
for_versions: <R::PathBuilder as PathBuilder>::Input<'_>,
6864
request: R,
6965
) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a + Send>>
7066
where
7167
R: OutgoingRequest,
72-
R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
68+
Self::RequestBody: AsRef<[u8]>,
69+
for<'b> R::Authentication: AuthScheme<Input<'b> = SendAccessToken<'b>>,
7370
{
74-
self.send_customized_matrix_request(
75-
homeserver_url,
76-
access_token,
77-
path_builder_input,
78-
request,
79-
|_| Ok(()),
80-
)
71+
Box::pin(crate::send_request(self, homeserver_url, access_token, for_versions, request))
8172
}
8273

8374
/// Turn a strongly-typed matrix request into an `http::Request`, customize it and send it to
@@ -106,30 +97,32 @@ pub trait HttpClientExt: HttpClient {
10697
))
10798
}
10899

109-
/// Turn a strongly-typed matrix request into an `http::Request`, add a `user_id` query
110-
/// parameter to it and send it to get back a strongly-typed response.
100+
/// Turn a strongly-typed matrix request into an `http::Request`, add `user_id` and/or
101+
/// `device_id` query parameters to it and send it to get back a strongly-typed response.
111102
///
112103
/// This method is meant to be used by application services when interacting with the
113104
/// client-server API.
114105
fn send_matrix_request_as<'a, R>(
115106
&'a self,
116107
homeserver_url: &str,
117-
access_token: SendAccessToken<'a>,
118-
path_builder_input: <R::PathBuilder as PathBuilder>::Input<'_>,
119-
user_id: &'a UserId,
108+
access_token: SendAccessToken<'_>,
109+
identity: AppserviceUserIdentity<'_>,
110+
for_versions: <R::PathBuilder as PathBuilder>::Input<'_>,
120111
request: R,
121-
) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>>
112+
) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a + Send>>
122113
where
123114
R: OutgoingRequest,
124-
R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
115+
Self::RequestBody: AsRef<[u8]>,
116+
for<'b> R::Authentication: AuthScheme<Input<'b> = SendAccessToken<'b>>,
125117
{
126-
self.send_customized_matrix_request(
118+
Box::pin(crate::send_request_as(
119+
self,
127120
homeserver_url,
128121
access_token,
129-
path_builder_input,
122+
identity,
123+
for_versions,
130124
request,
131-
add_user_id_to_query::<Self, R>(user_id),
132-
)
125+
))
133126
}
134127
}
135128

0 commit comments

Comments
 (0)