Skip to main content

ruma_client_api/membership/
join_room_by_id_or_alias.rs

1//! `POST /_matrix/client/*/join/{roomIdOrAlias}`
2//!
3//! Join a room using its ID or one of its aliases.
4
5pub mod v3 {
6    //! `/v3/` ([spec])
7    //!
8    //! [spec]: https://spec.matrix.org/v1.19/client-server-api/#post_matrixclientv3joinroomidoralias
9
10    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    /// Request type for the `join_room_by_id_or_alias` endpoint.
29    #[derive(Clone, Debug)]
30    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
31    pub struct Request {
32        /// The room where the user should be invited.
33        pub room_id_or_alias: OwnedRoomOrAliasId,
34
35        /// The signature of a `m.third_party_invite` token to prove that this user owns a third
36        /// party identity which has been invited to the room.
37        pub third_party_signed: Option<ThirdPartySigned>,
38
39        /// Optional reason for joining the room.
40        pub reason: Option<String>,
41
42        /// The servers to attempt to join the room through.
43        ///
44        /// One of the servers must be participating in the room.
45        ///
46        /// When serializing, this field is mapped to both `server_name` and `via`
47        /// with identical values.
48        ///
49        /// When deserializing, the value is read from `via` if it's not missing or
50        /// empty and `server_name` otherwise.
51        pub via: Vec<OwnedServerName>,
52    }
53
54    /// Data in the request's query string.
55    #[cfg_attr(feature = "client", derive(serde::Serialize))]
56    #[cfg_attr(feature = "server", derive(serde::Deserialize))]
57    struct RequestQuery {
58        /// The servers to attempt to join the room through.
59        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
60        via: Vec<OwnedServerName>,
61
62        /// The servers to attempt to join the room through.
63        ///
64        /// Deprecated in Matrix >1.11 in favour of `via`.
65        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
66        server_name: Vec<OwnedServerName>,
67    }
68
69    /// Data in the request's body.
70    #[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        /// The signature of a `m.third_party_invite` token to prove that this user owns a third
75        /// party identity which has been invited to the room.
76        #[serde(skip_serializing_if = "Option::is_none")]
77        third_party_signed: Option<ThirdPartySigned>,
78
79        /// Optional reason for joining the room.
80        #[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            // Only send `server_name` if the `via` parameter is not supported by the server.
98            // `via` was introduced in Matrix 1.12.
99            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                .header(http::header::CONTENT_TYPE, ruma_common::http_headers::APPLICATION_JSON)
122                .body(RequestBody {
123                    third_party_signed: self.third_party_signed,
124                    reason: self.reason,
125                })?;
126
127            Ok(http_request)
128        }
129    }
130
131    #[cfg(feature = "server")]
132    impl ruma_common::api::IncomingRequest for Request {
133        type EndpointError = Error;
134        type OutgoingResponse = Response;
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            Self::check_request_method(request.method())?;
145
146            let (room_id_or_alias,) =
147                serde::Deserialize::deserialize(serde::de::value::SeqDeserializer::<
148                    _,
149                    serde::de::value::Error,
150                >::new(
151                    path_args.iter().map(::std::convert::AsRef::as_ref),
152                ))?;
153
154            let request_query: RequestQuery =
155                serde_html_form::from_str(request.uri().query().unwrap_or(""))?;
156            let via = if request_query.via.is_empty() {
157                request_query.server_name
158            } else {
159                request_query.via
160            };
161
162            let body: RequestBody = serde_json::from_slice(request.body().as_ref())?;
163
164            Ok(Self {
165                room_id_or_alias,
166                reason: body.reason,
167                third_party_signed: body.third_party_signed,
168                via,
169            })
170        }
171    }
172
173    /// Response type for the `join_room_by_id_or_alias` endpoint.
174    #[response]
175    pub struct Response {
176        /// The room that the user joined.
177        pub room_id: OwnedRoomId,
178    }
179
180    impl Request {
181        /// Creates a new `Request` with the given room ID or alias ID.
182        pub fn new(room_id_or_alias: OwnedRoomOrAliasId) -> Self {
183            Self { room_id_or_alias, via: vec![], third_party_signed: None, reason: None }
184        }
185    }
186
187    impl Response {
188        /// Creates a new `Response` with the given room ID.
189        pub fn new(room_id: OwnedRoomId) -> Self {
190            Self { room_id }
191        }
192    }
193
194    #[cfg(all(test, feature = "client"))]
195    mod tests_client {
196        use std::borrow::Cow;
197
198        use ruma_common::{
199            api::{
200                MatrixVersion, OutgoingRequestExt as _, SupportedVersions,
201                auth_scheme::SendAccessToken,
202            },
203            owned_room_id, owned_server_name,
204        };
205
206        use super::Request;
207
208        #[test]
209        fn serialize_request_via_and_server_name() {
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_1].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&server_name=f.oo"));
225        }
226
227        #[test]
228        fn serialize_request_only_via() {
229            let mut req = Request::new(owned_room_id!("!foo:b.ar").into());
230            req.via = vec![owned_server_name!("f.oo")];
231            let supported = SupportedVersions {
232                versions: [MatrixVersion::V1_13].into(),
233                features: Default::default(),
234            };
235
236            let req = req
237                .try_into_http_request::<Vec<u8>>(
238                    "https://matrix.org",
239                    SendAccessToken::IfRequired("tok"),
240                    Cow::Owned(supported),
241                )
242                .unwrap();
243            assert_eq!(req.uri().query(), Some("via=f.oo"));
244        }
245    }
246
247    #[cfg(all(test, feature = "server"))]
248    mod tests_server {
249        use ruma_common::{api::IncomingRequest as _, owned_server_name};
250
251        use super::Request;
252
253        #[test]
254        fn deserialize_request_wrong_method() {
255            Request::try_from_http_request(
256                http::Request::builder()
257                    .method(http::Method::GET)
258                    .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo")
259                    .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
260                    .unwrap(),
261                &["!foo:b.ar"],
262            )
263            .expect_err("Should not deserialize request with illegal method");
264        }
265
266        #[test]
267        fn deserialize_request_only_via() {
268            let req = Request::try_from_http_request(
269                http::Request::builder()
270                    .method(http::Method::POST)
271                    .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo")
272                    .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
273                    .unwrap(),
274                &["!foo:b.ar"],
275            )
276            .unwrap();
277
278            assert_eq!(req.room_id_or_alias, "!foo:b.ar");
279            assert_eq!(req.reason, Some("Let me in already!".to_owned()));
280            assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
281        }
282
283        #[test]
284        fn deserialize_request_only_server_name() {
285            let req = Request::try_from_http_request(
286                http::Request::builder()
287                    .method(http::Method::POST)
288                    .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?server_name=f.oo")
289                    .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
290                    .unwrap(),
291                &["!foo:b.ar"],
292            )
293            .unwrap();
294
295            assert_eq!(req.room_id_or_alias, "!foo:b.ar");
296            assert_eq!(req.reason, Some("Let me in already!".to_owned()));
297            assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
298        }
299
300        #[test]
301        fn deserialize_request_via_and_server_name() {
302            let req = Request::try_from_http_request(
303                http::Request::builder()
304                    .method(http::Method::POST)
305                    .uri("https://matrix.org/_matrix/client/v3/join/!foo:b.ar?via=f.oo&server_name=b.ar")
306                    .body(b"{ \"reason\": \"Let me in already!\" }" as &[u8])
307                    .unwrap(),
308                &["!foo:b.ar"],
309            )
310            .unwrap();
311
312            assert_eq!(req.room_id_or_alias, "!foo:b.ar");
313            assert_eq!(req.reason, Some("Let me in already!".to_owned()));
314            assert_eq!(req.via, vec![owned_server_name!("f.oo")]);
315        }
316    }
317}