ruma_client_api/membership/
join_room_by_id_or_alias.rs1pub mod v3 {
6 use ruma_common::{
11 OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
12 api::{Metadata, 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::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, any(feature = "client", feature = "server")))]
198 mod tests {
199 #[cfg(feature = "client")]
200 use std::borrow::Cow;
201
202 use ruma_common::{
203 api::{
204 IncomingRequest as _, MatrixVersion, OutgoingRequest, SupportedVersions,
205 auth_scheme::SendAccessToken,
206 },
207 owned_room_id, owned_server_name,
208 };
209
210 use super::Request;
211
212 #[cfg(feature = "client")]
213 #[test]
214 fn serialize_request_via_and_server_name() {
215 let mut req = Request::new(owned_room_id!("!foo:b.ar").into());
216 req.via = vec![owned_server_name!("f.oo")];
217 let supported = SupportedVersions {
218 versions: [MatrixVersion::V1_1].into(),
219 features: Default::default(),
220 };
221
222 let req = req
223 .try_into_http_request::<Vec<u8>>(
224 "https://matrix.org",
225 SendAccessToken::IfRequired("tok"),
226 Cow::Owned(supported),
227 )
228 .unwrap();
229 assert_eq!(req.uri().query(), Some("via=f.oo&server_name=f.oo"));
230 }
231
232 #[cfg(feature = "client")]
233 #[test]
234 fn serialize_request_only_via() {
235 let mut req = Request::new(owned_room_id!("!foo:b.ar").into());
236 req.via = vec![owned_server_name!("f.oo")];
237 let supported = SupportedVersions {
238 versions: [MatrixVersion::V1_13].into(),
239 features: Default::default(),
240 };
241
242 let req = req
243 .try_into_http_request::<Vec<u8>>(
244 "https://matrix.org",
245 SendAccessToken::IfRequired("tok"),
246 Cow::Owned(supported),
247 )
248 .unwrap();
249 assert_eq!(req.uri().query(), Some("via=f.oo"));
250 }
251
252 #[cfg(feature = "server")]
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 #[cfg(feature = "server")]
267 #[test]
268 fn deserialize_request_only_via() {
269 let req = Request::try_from_http_request(
270 http::Request::builder()
271 .method(http::Method::POST)
272 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo")
273 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
274 .unwrap(),
275 &["!foo:b.ar"],
276 )
277 .unwrap();
278
279 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
280 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
281 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
282 }
283
284 #[cfg(feature = "server")]
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 #[cfg(feature = "server")]
303 #[test]
304 fn deserialize_request_via_and_server_name() {
305 let req = Request::try_from_http_request(
306 http::Request::builder()
307 .method(http::Method::POST)
308 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo&server_name=b.ar")
309 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
310 .unwrap(),
311 &["!foo:b.ar"],
312 )
313 .unwrap();
314
315 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
316 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
317 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
318 }
319 }
320}