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