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