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