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 .body(RequestBody {
122 third_party_signed: self.third_party_signed,
123 reason: self.reason,
124 })?;
125
126 Ok(http_request)
127 }
128 }
129
130 #[cfg(feature = "server")]
131 impl ruma_common::api::IncomingRequest for Request {
132 type EndpointError = Error;
133 type OutgoingResponse = Response;
134
135 fn try_from_http_request_inner(
136 request: http::Request<&[u8]>,
137 path_args: &[&str],
138 ) -> Result<Self, ruma_common::api::error::DeserializationError> {
139 let (room_id_or_alias,) =
140 serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
141 _,
142 serde::de::value::Error,
143 >::new(path_args.iter().copied()))?;
144
145 let request_query: RequestQuery =
146 serde_html_form::from_str(request.uri().query().unwrap_or(""))?;
147 let via = if request_query.via.is_empty() {
148 request_query.server_name
149 } else {
150 request_query.via
151 };
152
153 let body: RequestBody = serde_json::from_slice(request.body())?;
154
155 Ok(Self {
156 room_id_or_alias,
157 reason: body.reason,
158 third_party_signed: body.third_party_signed,
159 via,
160 })
161 }
162 }
163
164 #[response]
166 pub struct Response {
167 pub room_id: OwnedRoomId,
169 }
170
171 impl Request {
172 pub fn new(room_id_or_alias: OwnedRoomOrAliasId) -> Self {
174 Self { room_id_or_alias, via: vec![], third_party_signed: None, reason: None }
175 }
176 }
177
178 impl Response {
179 pub fn new(room_id: OwnedRoomId) -> Self {
181 Self { room_id }
182 }
183 }
184
185 #[cfg(all(test, feature = "client"))]
186 mod tests_client {
187 use std::borrow::Cow;
188
189 use ruma_common::{
190 api::{
191 MatrixVersion, OutgoingRequestExt as _, SupportedVersions,
192 auth_scheme::SendAccessToken,
193 },
194 owned_room_id, owned_server_name,
195 };
196
197 use super::Request;
198
199 #[test]
200 fn serialize_request_via_and_server_name() {
201 let mut req = Request::new(owned_room_id!("!foo:b.ar").into());
202 req.via = vec![owned_server_name!("f.oo")];
203 let supported = SupportedVersions {
204 versions: [MatrixVersion::V1_1].into(),
205 features: Default::default(),
206 };
207
208 let req = req
209 .try_into_http_request::<Vec<u8>>(
210 "https://matrix.org",
211 SendAccessToken::IfRequired("tok"),
212 Cow::Owned(supported),
213 )
214 .unwrap();
215 assert_eq!(req.uri().query(), Some("via=f.oo&server_name=f.oo"));
216 }
217
218 #[test]
219 fn serialize_request_only_via() {
220 let mut req = Request::new(owned_room_id!("!foo:b.ar").into());
221 req.via = vec![owned_server_name!("f.oo")];
222 let supported = SupportedVersions {
223 versions: [MatrixVersion::V1_13].into(),
224 features: Default::default(),
225 };
226
227 let req = req
228 .try_into_http_request::<Vec<u8>>(
229 "https://matrix.org",
230 SendAccessToken::IfRequired("tok"),
231 Cow::Owned(supported),
232 )
233 .unwrap();
234 assert_eq!(req.uri().query(), Some("via=f.oo"));
235 }
236 }
237
238 #[cfg(all(test, feature = "server"))]
239 mod tests_server {
240 use ruma_common::{api::IncomingRequestExt as _, owned_server_name};
241
242 use super::Request;
243
244 #[test]
245 fn deserialize_request_wrong_method() {
246 Request::try_from_http_request(
247 http::Request::builder()
248 .method(http::Method::GET)
249 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo")
250 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
251 .unwrap(),
252 &["!foo:b.ar"],
253 )
254 .expect_err("Should not deserialize request with illegal method");
255 }
256
257 #[test]
258 fn deserialize_request_only_via() {
259 let req = Request::try_from_http_request(
260 http::Request::builder()
261 .method(http::Method::POST)
262 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo")
263 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
264 .unwrap(),
265 &["!foo:b.ar"],
266 )
267 .unwrap();
268
269 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
270 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
271 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
272 }
273
274 #[test]
275 fn deserialize_request_only_server_name() {
276 let req = Request::try_from_http_request(
277 http::Request::builder()
278 .method(http::Method::POST)
279 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?server_name=f.oo")
280 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
281 .unwrap(),
282 &["!foo:b.ar"],
283 )
284 .unwrap();
285
286 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
287 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
288 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
289 }
290
291 #[test]
292 fn deserialize_request_via_and_server_name() {
293 let req = Request::try_from_http_request(
294 http::Request::builder()
295 .method(http::Method::POST)
296 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo&server_name=b.ar")
297 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
298 .unwrap(),
299 &["!foo:b.ar"],
300 )
301 .unwrap();
302
303 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
304 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
305 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
306 }
307 }
308}