ruma_client_api/membership/
join_room_by_id_or_alias.rs1pub mod v3 {
6 use ruma_common::{
11 api::{response, Metadata},
12 metadata, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName,
13 };
14
15 use crate::membership::ThirdPartySigned;
16
17 const METADATA: Metadata = 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 const METADATA: Metadata = METADATA;
88
89 fn try_into_http_request<T: Default + bytes::BufMut>(
90 self,
91 base_url: &str,
92 access_token: ruma_common::api::SendAccessToken<'_>,
93 considering: &'_ ruma_common::api::SupportedVersions,
94 ) -> Result<http::Request<T>, ruma_common::api::error::IntoHttpError> {
95 use http::header::{self, HeaderValue};
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(METADATA.method)
115 .uri(METADATA.make_endpoint_url(
116 considering,
117 base_url,
118 &[&self.room_id_or_alias],
119 &query_string,
120 )?)
121 .header(header::CONTENT_TYPE, "application/json")
122 .header(
123 header::AUTHORIZATION,
124 HeaderValue::from_str(&format!(
125 "Bearer {}",
126 access_token
127 .get_required_for_endpoint()
128 .ok_or(ruma_common::api::error::IntoHttpError::NeedsAuthentication)?
129 ))?,
130 )
131 .body(ruma_common::serde::json_to_buf(&RequestBody {
132 third_party_signed: self.third_party_signed,
133 reason: self.reason,
134 })?)?;
135
136 Ok(http_request)
137 }
138 }
139
140 #[cfg(feature = "server")]
141 impl ruma_common::api::IncomingRequest for Request {
142 type EndpointError = crate::Error;
143 type OutgoingResponse = Response;
144
145 const METADATA: Metadata = METADATA;
146
147 fn try_from_http_request<B, S>(
148 request: http::Request<B>,
149 path_args: &[S],
150 ) -> Result<Self, ruma_common::api::error::FromHttpRequestError>
151 where
152 B: AsRef<[u8]>,
153 S: AsRef<str>,
154 {
155 if request.method() != METADATA.method {
156 return Err(ruma_common::api::error::FromHttpRequestError::MethodMismatch {
157 expected: METADATA.method,
158 received: request.method().clone(),
159 });
160 }
161
162 let (room_id_or_alias,) =
163 serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
164 _,
165 serde::de::value::Error,
166 >::new(
167 path_args.iter().map(::std::convert::AsRef::as_ref),
168 ))?;
169
170 let request_query: RequestQuery =
171 serde_html_form::from_str(request.uri().query().unwrap_or(""))?;
172 let via = if request_query.via.is_empty() {
173 request_query.server_name
174 } else {
175 request_query.via
176 };
177
178 let body: RequestBody = serde_json::from_slice(request.body().as_ref())?;
179
180 Ok(Self {
181 room_id_or_alias,
182 reason: body.reason,
183 third_party_signed: body.third_party_signed,
184 via,
185 })
186 }
187 }
188
189 #[response(error = crate::Error)]
191 pub struct Response {
192 pub room_id: OwnedRoomId,
194 }
195
196 impl Request {
197 pub fn new(room_id_or_alias: OwnedRoomOrAliasId) -> Self {
199 Self { room_id_or_alias, via: vec![], third_party_signed: None, reason: None }
200 }
201 }
202
203 impl Response {
204 pub fn new(room_id: OwnedRoomId) -> Self {
206 Self { room_id }
207 }
208 }
209
210 #[cfg(all(test, any(feature = "client", feature = "server")))]
211 mod tests {
212 use ruma_common::{
213 api::{
214 IncomingRequest as _, MatrixVersion, OutgoingRequest, SendAccessToken,
215 SupportedVersions,
216 },
217 owned_room_id, owned_server_name,
218 };
219
220 use super::Request;
221
222 #[cfg(feature = "client")]
223 #[test]
224 fn serialize_request_via_and_server_name() {
225 let mut req = Request::new(owned_room_id!("!foo:b.ar").into());
226 req.via = vec![owned_server_name!("f.oo")];
227 let supported = SupportedVersions {
228 versions: [MatrixVersion::V1_1].into(),
229 features: Default::default(),
230 };
231
232 let req = req
233 .try_into_http_request::<Vec<u8>>(
234 "https://matrix.org",
235 SendAccessToken::IfRequired("tok"),
236 &supported,
237 )
238 .unwrap();
239 assert_eq!(req.uri().query(), Some("via=f.oo&server_name=f.oo"));
240 }
241
242 #[cfg(feature = "client")]
243 #[test]
244 fn serialize_request_only_via() {
245 let mut req = Request::new(owned_room_id!("!foo:b.ar").into());
246 req.via = vec![owned_server_name!("f.oo")];
247 let supported = SupportedVersions {
248 versions: [MatrixVersion::V1_13].into(),
249 features: Default::default(),
250 };
251
252 let req = req
253 .try_into_http_request::<Vec<u8>>(
254 "https://matrix.org",
255 SendAccessToken::IfRequired("tok"),
256 &supported,
257 )
258 .unwrap();
259 assert_eq!(req.uri().query(), Some("via=f.oo"));
260 }
261
262 #[cfg(feature = "server")]
263 #[test]
264 fn deserialize_request_wrong_method() {
265 Request::try_from_http_request(
266 http::Request::builder()
267 .method(http::Method::GET)
268 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo")
269 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
270 .unwrap(),
271 &["!foo:b.ar"],
272 )
273 .expect_err("Should not deserialize request with illegal method");
274 }
275
276 #[cfg(feature = "server")]
277 #[test]
278 fn deserialize_request_only_via() {
279 let req = Request::try_from_http_request(
280 http::Request::builder()
281 .method(http::Method::POST)
282 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo")
283 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
284 .unwrap(),
285 &["!foo:b.ar"],
286 )
287 .unwrap();
288
289 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
290 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
291 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
292 }
293
294 #[cfg(feature = "server")]
295 #[test]
296 fn deserialize_request_only_server_name() {
297 let req = Request::try_from_http_request(
298 http::Request::builder()
299 .method(http::Method::POST)
300 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?server_name=f.oo")
301 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
302 .unwrap(),
303 &["!foo:b.ar"],
304 )
305 .unwrap();
306
307 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
308 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
309 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
310 }
311
312 #[cfg(feature = "server")]
313 #[test]
314 fn deserialize_request_via_and_server_name() {
315 let req = Request::try_from_http_request(
316 http::Request::builder()
317 .method(http::Method::POST)
318 .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo&server_name=b.ar")
319 .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
320 .unwrap(),
321 &["!foo:b.ar"],
322 )
323 .unwrap();
324
325 assert_eq!(req.room_id_or_alias, "!foo:b.ar");
326 assert_eq!(req.reason, Some("Let me in already!".to_owned()));
327 assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
328 }
329 }
330}