Skip to main content

ruma_client_api/rendezvous/
create_rendezvous_session.rs

1//! `POST /_matrix/client/*/rendezvous/`
2//!
3//! Create a rendezvous session.
4
5#[cfg(feature = "unstable-msc4108")]
6pub mod unstable_msc4108 {
7    //! `unstable/org.matrix.msc4108` ([MSC])
8    //!
9    //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
10
11    use http::header::{CONTENT_TYPE, ETAG, EXPIRES, LAST_MODIFIED};
12    #[cfg(feature = "client")]
13    use ruma_common::api::BytesBody;
14    use ruma_common::{
15        api::{
16            auth_scheme::NoAccessToken,
17            error::{DeserializationError, Error, HeaderDeserializationError},
18        },
19        http_headers::TEXT_PLAIN,
20        metadata,
21    };
22    use url::Url;
23    use web_time::SystemTime;
24
25    metadata! {
26        method: POST,
27        rate_limited: true,
28        authentication: NoAccessToken,
29        history: {
30            unstable("org.matrix.msc4108") => "/_matrix/client/unstable/org.matrix.msc4108/rendezvous",
31        }
32    }
33
34    /// Request type for the `POST` `rendezvous` endpoint from the 2024 version of MSC4108.
35    #[derive(Debug, Default, Clone)]
36    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
37    pub struct Request {
38        /// Any data up to maximum size allowed by the server.
39        pub content: String,
40    }
41
42    #[cfg(feature = "client")]
43    impl ruma_common::api::OutgoingRequest for Request {
44        type Body = BytesBody;
45        type EndpointError = Error;
46        type IncomingResponse = Response;
47
48        fn try_into_http_request_inner(
49            self,
50            base_url: &str,
51            considering: std::borrow::Cow<'_, ruma_common::api::SupportedVersions>,
52        ) -> Result<http::Request<BytesBody>, ruma_common::api::error::IntoHttpError> {
53            use http::header::CONTENT_LENGTH;
54            use ruma_common::api::Metadata;
55
56            let Self { content } = self;
57
58            let url = Self::make_endpoint_url(considering, base_url, &[], "")?;
59            let content_length = content.len();
60
61            Ok(http::Request::builder()
62                .method(Self::METHOD)
63                .uri(url)
64                .header(CONTENT_TYPE, TEXT_PLAIN)
65                .header(CONTENT_LENGTH, content_length)
66                .body(BytesBody(content.into()))?)
67        }
68    }
69
70    #[cfg(feature = "server")]
71    impl ruma_common::api::IncomingRequest for Request {
72        type EndpointError = Error;
73        type OutgoingResponse = Response;
74
75        fn try_from_http_request_inner(
76            request: http::Request<&[u8]>,
77            _path_args: &[&str],
78        ) -> Result<Self, DeserializationError> {
79            let content_type = request
80                .headers()
81                .get(CONTENT_TYPE)
82                .ok_or(HeaderDeserializationError::MissingHeader(CONTENT_TYPE.to_string()))?;
83
84            if content_type != TEXT_PLAIN {
85                Err(HeaderDeserializationError::InvalidHeaderValue {
86                    header: CONTENT_TYPE.to_string(),
87                    expected: TEXT_PLAIN
88                        .to_str()
89                        .expect("expected content type should be a valid static string")
90                        .to_owned(),
91                    unexpected: content_type.to_str()?.to_owned(),
92                }
93                .into())
94            } else {
95                let body = request.into_body().to_vec();
96                let content = String::from_utf8(body)
97                    .map_err(|e| DeserializationError::Utf8(e.utf8_error()))?;
98
99                Ok(Self { content })
100            }
101        }
102    }
103
104    impl Request {
105        /// Creates a new `Request` with the given content.
106        pub fn new(content: String) -> Self {
107            Self { content }
108        }
109    }
110
111    /// Response type for the `POST` `rendezvous` endpoint from the 2024 version of MSC4108.
112    #[derive(Debug, Clone)]
113    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
114    pub struct Response {
115        /// The absolute URL of the rendezvous session.
116        pub url: Url,
117
118        /// ETag for the current payload at the rendezvous session as
119        /// per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.etag).
120        pub etag: String,
121
122        /// The expiry time of the rendezvous as per
123        /// [RFC7234](https://httpwg.org/specs/rfc7234.html#header.expires).
124        pub expires: SystemTime,
125
126        /// The last modified date of the payload as
127        /// per [RFC7232](https://httpwg.org/specs/rfc7232.html#header.last-modified)
128        pub last_modified: SystemTime,
129    }
130
131    #[doc(hidden)]
132    #[derive(ruma_common::serde::_FakeDeriveSerde)]
133    #[cfg_attr(feature = "server", derive(serde::Serialize, ruma_common::api::OutgoingBodyJson))]
134    #[cfg_attr(feature = "client", derive(serde::Deserialize))]
135    pub struct ResponseBody {
136        url: Url,
137    }
138
139    #[cfg(feature = "client")]
140    impl ruma_common::api::IncomingResponse for Response {
141        type EndpointError = Error;
142
143        fn try_from_http_response_inner(
144            response: http::Response<&[u8]>,
145        ) -> Result<Self, DeserializationError> {
146            let get_date = |header: http::HeaderName| -> Result<SystemTime, DeserializationError> {
147                let date = response
148                    .headers()
149                    .get(&header)
150                    .ok_or_else(|| HeaderDeserializationError::MissingHeader(header.to_string()))?;
151
152                let date = ruma_common::http_headers::http_date_to_system_time(date)?;
153
154                Ok(date)
155            };
156
157            let etag = response
158                .headers()
159                .get(ETAG)
160                .ok_or(HeaderDeserializationError::MissingHeader(ETAG.to_string()))?
161                .to_str()?
162                .to_owned();
163            let expires = get_date(EXPIRES)?;
164            let last_modified = get_date(LAST_MODIFIED)?;
165
166            let ResponseBody { url } = serde_json::from_slice(response.body())?;
167
168            Ok(Self { url, etag, expires, last_modified })
169        }
170    }
171
172    #[cfg(feature = "server")]
173    impl ruma_common::api::OutgoingResponse for Response {
174        type Body = ResponseBody;
175
176        fn try_into_http_response_inner(
177            self,
178        ) -> Result<http::Response<Self::Body>, ruma_common::api::error::IntoHttpError> {
179            use http::header::{CACHE_CONTROL, PRAGMA};
180            use ruma_common::http_headers::system_time_to_http_date;
181
182            let Self { url, etag, expires, last_modified } = self;
183
184            let body = ResponseBody { url };
185
186            let expires = system_time_to_http_date(&expires)?;
187            let last_modified = system_time_to_http_date(&last_modified)?;
188
189            Ok(http::Response::builder()
190                .status(http::StatusCode::OK)
191                .header(PRAGMA, "no-cache")
192                .header(CACHE_CONTROL, "no-store")
193                .header(ETAG, etag)
194                .header(EXPIRES, expires)
195                .header(LAST_MODIFIED, last_modified)
196                .body(body)?)
197        }
198    }
199}
200
201#[cfg(feature = "unstable-msc4388")]
202pub mod unstable_msc4388 {
203    //! `unstable/io.element.msc4388` ([MSC])
204    //!
205    //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4388
206    use std::time::Duration;
207
208    use ruma_common::{
209        api::{auth_scheme::AccessTokenOptional, request, response},
210        metadata,
211    };
212
213    metadata! {
214        method: POST,
215        rate_limited: true,
216        authentication: AccessTokenOptional,
217        history: {
218            unstable => "/_matrix/client/unstable/io.element.msc4388/rendezvous",
219        }
220    }
221
222    /// Request type for the `POST` `rendezvous` endpoint.
223    #[request]
224    pub struct Request {
225        /// Data up to maximum size allowed by the server.
226        pub data: String,
227    }
228
229    impl Request {
230        /// Creates a new `Request` with the given content.
231        pub fn new(data: String) -> Self {
232            Self { data }
233        }
234    }
235
236    /// Response type for the `POST` `rendezvous` endpoint.
237    #[response]
238    pub struct Response {
239        /// The ID of the created rendezvous session.
240        pub id: String,
241
242        /// The initial sequence token for the session.
243        pub sequence_token: String,
244
245        /// The time remaining in milliseconds until the session expires.
246        #[serde(with = "ruma_common::serde::duration::ms", rename = "expires_in_ms")]
247        pub expires_in: Duration,
248    }
249
250    impl Response {
251        /// Creates a new `Response` with the given content.
252        pub fn new(id: String, sequence_token: String, expires_in: Duration) -> Self {
253            Self { id, sequence_token, expires_in }
254        }
255    }
256}