ruma_client_api/membership/
join_room_by_id_or_alias.rs1pub mod v3 {
6 use ruma_common::{
11 OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
12 api::{auth_scheme::AccessToken, response},
13 metadata,
14 };
15
16 use crate::membership::ThirdPartySigned;
17
18 metadata! {
19 method: POST,
20 rate_limited: true,
21 authentication: AccessToken,
22 history: {
23 1.0 => "/_matrix/client/r0/join/{room_id_or_alias}",
24 1.1 => "/_matrix/client/v3/join/{room_id_or_alias}",
25 }
26 }
27
28 #[derive(Clone, Debug)]
30 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
31 pub struct Request {
32 pub room_id_or_alias: OwnedRoomOrAliasId,
34
35 pub third_party_signed: Option<ThirdPartySigned>,
38
39 pub reason: Option<String>,
41
42 pub via: Vec<OwnedServerName>,
52 }
53
54 #[cfg_attr(feature = "client", derive(serde::Serialize))]
56 #[cfg_attr(feature = "server", derive(serde::Deserialize))]
57 struct RequestQuery {
58 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
60 via: Vec<OwnedServerName>,
61
62 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
66 server_name: Vec<OwnedServerName>,
67 }
68
69 #[cfg_attr(feature = "client", derive(serde::Serialize))]
71 #[cfg_attr(feature = "server", derive(serde::Deserialize))]
72 struct RequestBody {
73 #[serde(skip_serializing_if = "Option::is_none")]
76 third_party_signed: Option<ThirdPartySigned>,
77
78 #[serde(skip_serializing_if = "Option::is_none")]
80 reason: Option<String>,
81 }
82
83 #[cfg(feature = "client")]
84 impl ruma_common::api::OutgoingRequest for Request {
85 type EndpointError = crate::Error;
86 type IncomingResponse = Response;
87
88 fn try_into_http_request<T: Default + bytes::BufMut + AsRef<[u8]>>(
89 self,
90 base_url: &str,
91 access_token: ruma_common::api::auth_scheme::SendAccessToken<'_>,
92 considering: std::borrow::Cow<'_, ruma_common::api::SupportedVersions>,
93 ) -> Result<http::Request<T>, ruma_common::api::error::IntoHttpError> {
94 use ruma_common::api::{Metadata, auth_scheme::AuthScheme};
95
96 let server_name = if considering
99 .versions
100 .iter()
101 .rev()
102 .any(|version| version.is_superset_of(ruma_common::api::MatrixVersion::V1_12))
103 {
104 vec![]
105 } else {
106 self.via.clone()
107 };
108
109 let query_string =
110 serde_html_form::to_string(RequestQuery { server_name, via: self.via })?;
111
112 let mut http_request = http::Request::builder()
113 .method(Self::METHOD)
114 .uri(Self::make_endpoint_url(
115 considering,
116 base_url,
117 &[&self.room_id_or_alias],
118 &query_string,
119 )?)
120 .header(http::header::CONTENT_TYPE, ruma_common::http_headers::APPLICATION_JSON)
121 .body(ruma_common::serde::json_to_buf(&RequestBody {
122 third_party_signed: self.third_party_signed,
123 reason: self.reason,
124 })?)?;
125
126 Self::Authentication::add_authentication(&mut http_request, access_token).map_err(
127 |error| ruma_common::api::error::IntoHttpError::Authentication(error.into()),
128 )?;
129
130 Ok(http_request)
131 }
132 }
133
134 #[cfg(feature = "server")]
135 impl ruma_common::api::IncomingRequest for Request {
136 type EndpointError = crate::Error;
137 type OutgoingResponse = Response;
138
139 fn try_from_http_request<B, S>(
140 request: http::Request<B>,
141 path_args: &[S],
142 ) -> Result<Self, ruma_common::api::error::FromHttpRequestError>
143 where
144 B: AsRef<[u8]>,
145 S: AsRef<str>,
146 {
147 Self::check_request_method(request.method())?;
148
149 let (room_id_or_alias,) =
150 serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
151 _,
152 serde::de::value::Error,
153 >::new(
154 path_args.iter().map(::std::convert::AsRef::as_ref),
155 ))?;
156
157 let request_query: RequestQuery =
158 serde_html_form::from_str(request.uri().query().unwrap_or(""))?;
159 let via = if request_query.via.is_empty() {
160 request_query.server_name
161 } else {
162 request_query.via
163 };
164
165 let body: RequestBody = serde_json::from_slice(request.body().as_ref())?;
166
167 Ok(Self {
168 room_id_or_alias,
169 reason: body.reason,
170 third_party_signed: body.third_party_signed,
171 via,
172 })
173 }
174 }
175
176 #[response(error = crate::Error)]
178 pub struct Response {
179 pub room_id: OwnedRoomId,
181 }
182
183 impl Request {
184 pub fn new(room_id_or_alias: OwnedRoomOrAliasId) -> Self {
186 Self { room_id_or_alias, via: vec![], third_party_signed: None, reason: None }
187 }
188 }
189
190 impl Response {
191 pub fn new(room_id: OwnedRoomId) -> Self {
193 Self { room_id }
194 }
195 }
196
197 #[cfg(all(test, feature = "client"))]
198 mod tests_client {
199 use std::borrow::Cow;
200
201 use ruma_common::{
202 api::{
203 MatrixVersion, OutgoingRequest, SupportedVersions, auth_scheme::SendAccessToken,
204 },
205 owned_room_id, owned_server_name,
206 };
207
208 use super::Request;
209
210 #[test]
211 fn serialize_request_via_and_server_name() {
212 let mut req = Request::new(owned_room_id!("!foo:b.ar").into());
213 req.via = vec![owned_server_name!("f.oo")];
214 let supported = SupportedVersions {
215 versions: [MatrixVersion::V1_1].into(),
216 features: Default::default(),
217 };
218
219 let req = req
220 .try_into_http_request::<Vec<u8>>(
221 "https://matrix.org",
222 SendAccessToken::IfRequired("tok"),
223 Cow::Owned(supported),
224 )
225 .unwrap();
226 assert_eq!(req.uri().query(), Some("via=f.oo&server_name=f.oo"));
227 }
228
229 #[test]
230 fn serialize_request_only_via() {
231 let mut req = Request::new(owned_room_id!("!foo:b.ar").into());
232 req.via = vec![owned_server_name!("f.oo")];
233 let supported = SupportedVersions {
234 versions: [MatrixVersion::V1_13].into(),
235 features: Default::default(),
236 };
237
238 let req = req
239 .try_into_http_request::<Vec<u8>>(
240 "https://matrix.org",
241 SendAccessToken::IfRequired("tok"),
242 Cow::Owned(supported),
243 )
244 .unwrap();
245 assert_eq!(req.uri().query(), Some("via=f.oo"));
246 }
247 }
248
249 #[cfg(all(test, feature = "server"))]
250 mod tests_server {
251 use ruma_common::{api::IncomingRequest as _, owned_server_name};
252
253 use super::Request;
254
255 #[test]
256 fn deserialize_request_wrong_method() {
257 Request::try_from_http_request(
258 http::Request::builder()
259 .method(http::Method::GET)
260 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo")
261 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
262 .unwrap(),
263 &["!foo:b.ar"],
264 )
265 .expect_err("Should not deserialize request with illegal method");
266 }
267
268 #[test]
269 fn deserialize_request_only_via() {
270 let req = Request::try_from_http_request(
271 http::Request::builder()
272 .method(http::Method::POST)
273 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo")
274 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
275 .unwrap(),
276 &["!foo:b.ar"],
277 )
278 .unwrap();
279
280 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
281 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
282 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
283 }
284
285 #[test]
286 fn deserialize_request_only_server_name() {
287 let req = Request::try_from_http_request(
288 http::Request::builder()
289 .method(http::Method::POST)
290 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?server_name=f.oo")
291 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
292 .unwrap(),
293 &["!foo:b.ar"],
294 )
295 .unwrap();
296
297 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
298 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
299 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
300 }
301
302 #[test]
303 fn deserialize_request_via_and_server_name() {
304 let req = Request::try_from_http_request(
305 http::Request::builder()
306 .method(http::Method::POST)
307 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo&server_name=b.ar")
308 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
309 .unwrap(),
310 &["!foo:b.ar"],
311 )
312 .unwrap();
313
314 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
315 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
316 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
317 }
318 }
319}