ruma_common/
api.rs

1//! Core types used to define the requests and responses for each endpoint in the various
2//! [Matrix API specifications][apis].
3//!
4//! When implementing a new Matrix API, each endpoint has a request type which implements
5//! [`IncomingRequest`] and [`OutgoingRequest`], and a response type connected via an associated
6//! type.
7//!
8//! An implementation of [`IncomingRequest`] or [`OutgoingRequest`] contains all the information
9//! about the HTTP method, the path and input parameters for requests, and the structure of a
10//! successful response. Such types can then be used by client code to make requests, and by server
11//! code to fulfill those requests.
12//!
13//! [apis]: https://spec.matrix.org/latest/#matrix-apis
14
15use std::{convert::TryInto as _, error::Error as StdError};
16
17use bytes::BufMut;
18/// Generates [`OutgoingRequest`] and [`IncomingRequest`] implementations.
19///
20/// The `OutgoingRequest` impl is feature-gated behind `cfg(feature = "client")`.
21/// The `IncomingRequest` impl is feature-gated behind `cfg(feature = "server")`.
22///
23/// The generated code expects the `Request` type to implement [`Metadata`], alongside a
24/// `Response` type that implements [`OutgoingResponse`] (for `cfg(feature = "server")`) and /
25/// or [`IncomingResponse`] (for `cfg(feature = "client")`).
26///
27/// The `Content-Type` header of the `OutgoingRequest` is unset for endpoints using the `GET`
28/// method, and defaults to `application/json` for all other methods, except if the `raw_body`
29/// attribute is set on a field, in which case it defaults to `application/octet-stream`.
30///
31/// By default, the type this macro is used on gets a `#[non_exhaustive]` attribute. This
32/// behavior can be controlled by setting the `ruma_unstable_exhaustive_types` compile-time
33/// `cfg` setting as `--cfg=ruma_unstable_exhaustive_types` using `RUSTFLAGS` or
34/// `.cargo/config.toml` (under `[build]` -> `rustflags = ["..."]`). When that setting is
35/// activated, the attribute is not applied so the type is exhaustive.
36///
37/// ## Container Attributes
38///
39/// * `#[request(error = ERROR_TYPE)]`: Override the `EndpointError` associated type of the
40///   `OutgoingRequest` and `IncomingRequest` implementations. The default error type is
41///   [`MatrixError`](error::MatrixError).
42///
43/// ## Field Attributes
44///
45/// To declare which part of the request a field belongs to:
46///
47/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP
48///   headers on the request. The value must implement `ToString` and `FromStr`. Generally this
49///   is a `String`. The attribute value shown above as `HEADER_NAME` must be a `const`
50///   expression of the type `http::header::HeaderName`, like one of the constants from
51///   `http::header`, e.g. `CONTENT_TYPE`. During deserialization of the request, if the field
52///   is an `Option` and parsing the header fails, the error will be ignored and the value will
53///   be `None`.
54/// * `#[ruma_api(path)]`: Fields with this attribute will be inserted into the matching path
55///   component of the request URL. If there are multiple of these fields, the order in which
56///   they are declared must match the order in which they occur in the request path.
57/// * `#[ruma_api(query)]`: Fields with this attribute will be inserting into the URL's query
58///   string.
59/// * `#[ruma_api(query_all)]`: Instead of individual query fields, one query_all field, of any
60///   type that can be (de)serialized by [serde_html_form], can be used for cases where
61///   multiple endpoints should share a query fields type, the query fields are better
62///   expressed as an `enum` rather than a `struct`, or the endpoint supports arbitrary query
63///   parameters.
64/// * No attribute: Fields without an attribute are part of the body. They can use `#[serde]`
65///   attributes to customize (de)serialization.
66/// * `#[ruma_api(body)]`: Use this if multiple endpoints should share a request body type, or
67///   the request body is better expressed as an `enum` rather than a `struct`. The value of
68///   the field will be used as the JSON body (rather than being a field in the request body
69///   object).
70/// * `#[ruma_api(raw_body)]`: Like `body` in that the field annotated with it represents the
71///   entire request body, but this attribute is for endpoints where the body can be anything,
72///   not just JSON. The field type must be `Vec<u8>`.
73///
74/// ## Examples
75///
76/// ```
77/// pub mod do_a_thing {
78///     use ruma_common::{api::request, OwnedRoomId};
79///     # use ruma_common::{api::{auth_scheme::NoAuthentication, response}, metadata};
80///
81///     // metadata! { ... };
82///     # metadata! {
83///     #     method: POST,
84///     #     rate_limited: false,
85///     #     authentication: NoAuthentication,
86///     #     history: {
87///     #         unstable => "/_matrix/some/endpoint/{room_id}",
88///     #     },
89///     # }
90///
91///     #[request]
92///     pub struct Request {
93///         #[ruma_api(path)]
94///         pub room_id: OwnedRoomId,
95///
96///         #[ruma_api(query)]
97///         pub bar: String,
98///
99///         #[serde(default)]
100///         pub foo: String,
101///     }
102///
103///     // #[response]
104///     // pub struct Response { ... }
105///     # #[response]
106///     # pub struct Response {}
107/// }
108///
109/// pub mod upload_file {
110///     use http::header::CONTENT_TYPE;
111///     use ruma_common::api::request;
112///     # use ruma_common::{api::{auth_scheme::NoAuthentication, response}, metadata};
113///
114///     // metadata! { ... };
115///     # metadata! {
116///     #     method: POST,
117///     #     rate_limited: false,
118///     #     authentication: NoAuthentication,
119///     #     history: {
120///     #         unstable => "/_matrix/some/endpoint/{file_name}",
121///     #     },
122///     # }
123///
124///     #[request]
125///     pub struct Request {
126///         #[ruma_api(path)]
127///         pub file_name: String,
128///
129///         #[ruma_api(header = CONTENT_TYPE)]
130///         pub content_type: String,
131///
132///         #[ruma_api(raw_body)]
133///         pub file: Vec<u8>,
134///     }
135///
136///     // #[response]
137///     // pub struct Response { ... }
138///     # #[response]
139///     # pub struct Response {}
140/// }
141/// ```
142///
143/// [serde_html_form]: https://crates.io/crates/serde_html_form
144pub use ruma_macros::request;
145/// Generates [`OutgoingResponse`] and [`IncomingResponse`] implementations.
146///
147/// The `OutgoingResponse` impl is feature-gated behind `cfg(feature = "server")`.
148/// The `IncomingResponse` impl is feature-gated behind `cfg(feature = "client")`.
149///
150/// The `Content-Type` header of the `OutgoingResponse` defaults to `application/json`, except
151/// if the `raw_body` attribute is set on a field, in which case it defaults to
152/// `application/octet-stream`.
153///
154/// By default, the type this macro is used on gets a `#[non_exhaustive]` attribute. This
155/// behavior can be controlled by setting the `ruma_unstable_exhaustive_types` compile-time
156/// `cfg` setting as `--cfg=ruma_unstable_exhaustive_types` using `RUSTFLAGS` or
157/// `.cargo/config.toml` (under `[build]` -> `rustflags = ["..."]`). When that setting is
158/// activated, the attribute is not applied so the type is exhaustive.
159///
160/// ## Container Attributes
161///
162/// * `#[response(error = ERROR_TYPE)]`: Override the `EndpointError` associated type of the
163///   `IncomingResponse` implementation. The default error type is
164///   [`MatrixError`](error::MatrixError).
165/// * `#[response(status = HTTP_STATUS)]`: Override the status code of `OutgoingResponse`.
166///   `HTTP_STATUS` must be a status code constant from [`http::StatusCode`], e.g.
167///   `IM_A_TEAPOT`. The default status code is [`200 OK`](http::StatusCode::OK);
168///
169/// ## Field Attributes
170///
171/// To declare which part of the response a field belongs to:
172///
173/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP
174///   headers on the response. `HEADER_NAME` must implement
175///   `TryInto<http::header::HeaderName>`, this is usually a constant from [`http::header`].
176///   The value of the field must implement `ToString` and `FromStr`, this is usually a
177///   `String`. During deserialization of the response, if the field is an `Option` and parsing
178///   the header fails, the error will be ignored and the value will be `None`.
179/// * No attribute: Fields without an attribute are part of the body. They can use `#[serde]`
180///   attributes to customize (de)serialization.
181/// * `#[ruma_api(body)]`: Use this if multiple endpoints should share a response body type, or
182///   the response body is better expressed as an `enum` rather than a `struct`. The value of
183///   the field will be used as the JSON body (rather than being a field in the response body
184///   object).
185/// * `#[ruma_api(raw_body)]`: Like `body` in that the field annotated with it represents the
186///   entire response body, but this attribute is for endpoints where the body can be anything,
187///   not just JSON. The field type must be `Vec<u8>`.
188///
189/// ## Examples
190///
191/// ```
192/// pub mod do_a_thing {
193///     use ruma_common::{api::response, OwnedRoomId};
194///     # use ruma_common::{api::{auth_scheme::NoAuthentication, request}, metadata};
195///
196///     // metadata! { ... };
197///     # metadata! {
198///     #     method: POST,
199///     #     rate_limited: false,
200///     #     authentication: NoAuthentication,
201///     #     history: {
202///     #         unstable => "/_matrix/some/endpoint",
203///     #     },
204///     # }
205///
206///     // #[request]
207///     // pub struct Request { ... }
208///     # #[request]
209///     # pub struct Request { }
210///
211///     #[response(status = IM_A_TEAPOT)]
212///     pub struct Response {
213///         #[serde(skip_serializing_if = "Option::is_none")]
214///         pub foo: Option<String>,
215///     }
216/// }
217///
218/// pub mod download_file {
219///     use http::header::CONTENT_TYPE;
220///     use ruma_common::api::response;
221///     # use ruma_common::{api::{auth_scheme::NoAuthentication, request}, metadata};
222///
223///     // metadata! { ... };
224///     # metadata! {
225///     #     method: POST,
226///     #     rate_limited: false,
227///     #     authentication: NoAuthentication,
228///     #     history: {
229///     #         unstable => "/_matrix/some/endpoint",
230///     #     },
231///     # }
232///
233///     // #[request]
234///     // pub struct Request { ... }
235///     # #[request]
236///     # pub struct Request { }
237///
238///     #[response]
239///     pub struct Response {
240///         #[ruma_api(header = CONTENT_TYPE)]
241///         pub content_type: String,
242///
243///         #[ruma_api(raw_body)]
244///         pub file: Vec<u8>,
245///     }
246/// }
247/// ```
248pub use ruma_macros::response;
249use serde::{Deserialize, Serialize};
250
251use self::error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError};
252#[doc(inline)]
253pub use crate::metadata;
254use crate::{DeviceId, UserId};
255
256pub mod auth_scheme;
257pub mod error;
258mod metadata;
259pub mod path_builder;
260
261pub use self::metadata::{FeatureFlag, MatrixVersion, Metadata, SupportedVersions};
262
263/// A request type for a Matrix API endpoint, used for sending requests.
264pub trait OutgoingRequest: Metadata + Clone {
265    /// A type capturing the expected error conditions the server can return.
266    type EndpointError: EndpointError;
267
268    /// Response type returned when the request is successful.
269    type IncomingResponse: IncomingResponse<EndpointError = Self::EndpointError>;
270
271    /// Tries to convert this request into an `http::Request`.
272    ///
273    /// On endpoints with authentication, when adequate information isn't provided through
274    /// `authentication_input`, this could result in an error. It may also fail with a serialization
275    /// error in case of bugs in Ruma though.
276    ///
277    /// It may also fail if the `PathData::make_endpoint_url()` implementation returns an error.
278    ///
279    /// The endpoints path will be appended to the given `base_url`, for example
280    /// `https://matrix.org`. Since all paths begin with a slash, it is not necessary for the
281    /// `base_url` to have a trailing slash. If it has one however, it will be ignored.
282    fn try_into_http_request<T: Default + BufMut + AsRef<[u8]>>(
283        self,
284        base_url: &str,
285        authentication_input: <Self::Authentication as auth_scheme::AuthScheme>::Input<'_>,
286        path_builder_input: <Self::PathBuilder as path_builder::PathBuilder>::Input<'_>,
287    ) -> Result<http::Request<T>, IntoHttpError>;
288}
289
290/// A response type for a Matrix API endpoint, used for receiving responses.
291pub trait IncomingResponse: Sized {
292    /// A type capturing the expected error conditions the server can return.
293    type EndpointError: EndpointError;
294
295    /// Tries to convert the given `http::Response` into this response type.
296    fn try_from_http_response<T: AsRef<[u8]>>(
297        response: http::Response<T>,
298    ) -> Result<Self, FromHttpResponseError<Self::EndpointError>>;
299}
300
301/// An extension to [`OutgoingRequest`] which provides Appservice specific methods.
302///
303/// This is only implemented for implementors of [`AuthScheme`](auth_scheme::AuthScheme) that use a
304/// [`SendAccessToken`](auth_scheme::SendAccessToken), because application services should only use
305/// these methods with the Client-Server API.
306pub trait OutgoingRequestAppserviceExt: OutgoingRequest
307where
308    for<'a> Self::Authentication:
309        auth_scheme::AuthScheme<Input<'a> = auth_scheme::SendAccessToken<'a>>,
310{
311    /// Tries to convert this request into an `http::Request` and adds the given
312    /// [`AppserviceUserIdentity`] to it, if the identity is not empty.
313    fn try_into_http_request_with_identity<T: Default + BufMut + AsRef<[u8]>>(
314        self,
315        base_url: &str,
316        access_token: auth_scheme::SendAccessToken<'_>,
317        identity: AppserviceUserIdentity<'_>,
318        path_builder_input: <Self::PathBuilder as path_builder::PathBuilder>::Input<'_>,
319    ) -> Result<http::Request<T>, IntoHttpError> {
320        let mut http_request =
321            self.try_into_http_request(base_url, access_token, path_builder_input)?;
322
323        if !identity.is_empty() {
324            let identity_query = serde_html_form::to_string(identity)?;
325
326            let uri = http_request.uri().to_owned();
327            let mut parts = uri.into_parts();
328
329            let path_and_query_with_user_id = match &parts.path_and_query {
330                Some(path_and_query) => match path_and_query.query() {
331                    Some(_) => format!("{path_and_query}&{identity_query}"),
332                    None => format!("{path_and_query}?{identity_query}"),
333                },
334                None => format!("/?{identity_query}"),
335            };
336
337            parts.path_and_query =
338                Some(path_and_query_with_user_id.try_into().map_err(http::Error::from)?);
339
340            *http_request.uri_mut() = parts.try_into().map_err(http::Error::from)?;
341        }
342
343        Ok(http_request)
344    }
345}
346
347impl<T: OutgoingRequest> OutgoingRequestAppserviceExt for T where
348    for<'a> Self::Authentication:
349        auth_scheme::AuthScheme<Input<'a> = auth_scheme::SendAccessToken<'a>>
350{
351}
352
353/// A request type for a Matrix API endpoint, used for receiving requests.
354pub trait IncomingRequest: Metadata {
355    /// A type capturing the error conditions that can be returned in the response.
356    type EndpointError: EndpointError;
357
358    /// Response type to return when the request is successful.
359    type OutgoingResponse: OutgoingResponse;
360
361    /// Check whether the given HTTP method from an incoming request is compatible with the expected
362    /// [`METHOD`](Metadata::METHOD) of this endpoint.
363    fn check_request_method(method: &http::Method) -> Result<(), FromHttpRequestError> {
364        if !(method == Self::METHOD
365            || (Self::METHOD == http::Method::GET && method == http::Method::HEAD))
366        {
367            return Err(FromHttpRequestError::MethodMismatch {
368                expected: Self::METHOD,
369                received: method.clone(),
370            });
371        }
372
373        Ok(())
374    }
375
376    /// Tries to turn the given `http::Request` into this request type,
377    /// together with the corresponding path arguments.
378    ///
379    /// Note: The strings in path_args need to be percent-decoded.
380    fn try_from_http_request<B, S>(
381        req: http::Request<B>,
382        path_args: &[S],
383    ) -> Result<Self, FromHttpRequestError>
384    where
385        B: AsRef<[u8]>,
386        S: AsRef<str>;
387}
388
389/// A request type for a Matrix API endpoint, used for sending responses.
390pub trait OutgoingResponse {
391    /// Tries to convert this response into an `http::Response`.
392    ///
393    /// This method should only fail when when invalid header values are specified. It may also
394    /// fail with a serialization error in case of bugs in Ruma though.
395    fn try_into_http_response<T: Default + BufMut>(
396        self,
397    ) -> Result<http::Response<T>, IntoHttpError>;
398}
399
400/// Gives users the ability to define their own serializable / deserializable errors.
401pub trait EndpointError: OutgoingResponse + StdError + Sized + Send + 'static {
402    /// Tries to construct `Self` from an `http::Response`.
403    ///
404    /// This will always return `Err` variant when no `error` field is defined in
405    /// the `ruma_api` macro.
406    fn from_http_response<T: AsRef<[u8]>>(response: http::Response<T>) -> Self;
407}
408
409/// The direction to return events from.
410#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
411#[allow(clippy::exhaustive_enums)]
412pub enum Direction {
413    /// Return events backwards in time from the requested `from` token.
414    #[default]
415    #[serde(rename = "b")]
416    Backward,
417
418    /// Return events forwards in time from the requested `from` token.
419    #[serde(rename = "f")]
420    Forward,
421}
422
423/// Data to [assert the identity] of an appservice virtual user.
424///
425/// [assert the identity]: https://spec.matrix.org/latest/application-service-api/#identity-assertion
426#[derive(Debug, Clone, Copy, Default, Serialize)]
427#[non_exhaustive]
428pub struct AppserviceUserIdentity<'a> {
429    /// The ID of the virtual user.
430    ///
431    /// If this is not set, the user implied by the `sender_localpart` property of the registration
432    /// will be used by the server.
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub user_id: Option<&'a UserId>,
435
436    /// The ID of a specific device belonging to the virtual user.
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub device_id: Option<&'a DeviceId>,
439}
440
441impl<'a> AppserviceUserIdentity<'a> {
442    /// Construct a new `AppserviceUserIdentity` with the given user ID.
443    pub fn new(user_id: &'a UserId) -> Self {
444        Self { user_id: Some(user_id), device_id: None }
445    }
446
447    /// Whether this identity is empty.
448    fn is_empty(&self) -> bool {
449        self.user_id.is_none() && self.device_id.is_none()
450    }
451}