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