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