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