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