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