Skip to main content

ruma_common/api/
metadata.rs

1use std::{
2    cmp::Ordering,
3    collections::{BTreeMap, BTreeSet},
4    fmt::Display,
5    str::FromStr,
6};
7
8use http::Method;
9use ruma_macros::StringEnum;
10
11use super::{auth_scheme::AuthScheme, error::UnknownVersionError, path_builder::PathBuilder};
12use crate::{
13    PrivOwnedStr, RoomVersionId,
14    api::{auth_scheme::ClientScopedAuthScheme, error::IntoHttpError},
15};
16
17/// Convenient constructor for [`Metadata`] implementation.
18///
19/// ## Definition
20///
21/// By default, `Metadata` is implemented on a type named `Request` that is in scope. This can be
22/// overridden by adding `@for MyType` at the beginning of the declaration.
23///
24/// The rest of the definition of the macro is made to look like a struct, with the following
25/// fields:
26///
27/// * `method` - The HTTP method to use for the endpoint. Its value must be one of the associated
28///   constants of [`http::Method`]. In most cases it should be one of `GET`, `POST`, `PUT` or
29///   `DELETE`.
30/// * `rate_limited` - Whether the endpoint should be rate-limited, according to the specification.
31///   Its value must be a `bool`.
32/// * `authentication` - The type of authentication that is required for the endpoint, according to
33///   the specification. The type must be in scope and implement [`AuthScheme`].
34/// * `required_client_scopes` - The OAuth scopes which are required to access this endpoint, for clients
35///   authenticated using the [OAuth 2.0 API](https://spec.matrix.org/v1.19/client-server-api/#oauth-20-api).
36///   Its value must be an array of [`OAuthClientScope`]s. It defaults to `[OAuthClientScope::ApiFullAccess]`.
37///
38/// And either of the following fields to define the path(s) of the endpoint.
39///
40/// * `history` - The history of the paths of the endpoint. This should be used for endpoints from
41///   Matrix APIs that have a `/versions` endpoint that returns a list a [`MatrixVersion`]s and
42///   possibly features, like the Client-Server API or the Identity Service API. However, a few
43///   endpoints from those APIs shouldn't use this field because they cannot be versioned, like the
44///   `/versions` or the `/.well-known` endpoints.
45///
46///   Its definition is made to look like match arms and must include at least one arm. The match
47///   arms accept the following syntax:
48///
49///   * `unstable => "unstable/endpoint/path/{variable}"` - An unstable version of the endpoint as
50///     defined in the MSC that adds it, if the MSC does **NOT** define an unstable feature in the
51///     `unstable_features` field of the client-server API's `/versions` endpoint.
52///   * `unstable("org.bar.unstable_feature") => "unstable/endpoint/path/{variable}"` - An unstable
53///     version of the endpoint as defined in the MSC that adds it, if the MSC defines an unstable
54///     feature in the `unstable_features` field of the client-server API's `/versions` endpoint.
55///   * `1.0 | stable("org.bar.feature.stable") => "stable/endpoint/path/{variable}"` - A stable
56///     version of the endpoint as defined in an MSC or the Matrix specification. The match arm can
57///     be a Matrix version, a stable feature, or both separated by `|`.
58///
59///     A stable feature can be defined in an MSC alongside an unstable feature, and can be found in
60///     the `unstable_features` field of the client-server API's `/versions` endpoint. It is meant
61///     to be used by homeservers if they want to declare stable support for a feature before they
62///     can declare support for a whole Matrix version that supports it.
63///
64///   * `1.2 => deprecated` - The Matrix version that deprecated the endpoint, if any. It must be
65///     preceded by a match arm with a stable path and a different Matrix version.
66///   * `1.3 => removed` - The Matrix version that removed the endpoint, if any. It must be preceded
67///     by a match arm with a deprecation and a different Matrix version.
68///
69///   A Matrix version is a `float` representation of the version that looks like `major.minor`.
70///   It must match one of the variants of [`MatrixVersion`]. For example `1.0` matches
71///   [`MatrixVersion::V1_0`], `1.1` matches [`MatrixVersion::V1_1`], etc.
72///
73///   It is expected that the match arms are ordered by descending age. Usually the older unstable
74///   paths would be before the newer unstable paths, then we would find the stable paths, and
75///   finally the deprecation and removal.
76///
77///   The following checks occur at compile time:
78///
79///   * All unstable and stable paths contain the same variables (or lack thereof).
80///   * Matrix versions in match arms are all different and in ascending order.
81///
82///   This field is represented as the [`VersionHistory`](super::path_builder::VersionHistory) type
83///   in the generated implementation.
84/// * `path` - The only path of the endpoint. This should be used for endpoints from Matrix APIs
85///   that do NOT have a `/versions` endpoint that returns a list a [`MatrixVersion`]s, like the
86///   Server-Server API or the Appservice API. It should also be used for endpoints that cannot be
87///   versioned, like the `/versions` or the `/.well-known` endpoints.
88///
89///   Its value must be a static string representing the path, like `"endpoint/path/{variable}"`.
90///
91///   This field is represented as the [`SinglePath`](super::path_builder::SinglePath) type in the
92///   generated implementation.
93///
94/// ## Example
95///
96/// ```
97/// use ruma_common::{
98///     api::{
99///         OAuthClientScope,
100///         auth_scheme::{AccessToken, NoAuthentication},
101///     },
102///     metadata,
103/// };
104///
105/// /// A Request with a path version history.
106/// pub struct Request {
107///     body: Vec<u8>,
108/// }
109///
110/// metadata! {
111///     method: GET,
112///     rate_limited: true,
113///     authentication: AccessToken,
114///     // unnecessary here because that's the default
115///     required_client_scopes: [OAuthClientScope::ApiFullAccess],
116///
117///     history: {
118///         unstable => "/_matrix/unstable/org.bar.msc9000/baz",
119///         unstable("org.bar.msc9000.v1") => "/_matrix/unstable/org.bar.msc9000.v1/qux",
120///         1.0 | stable("org.bar.msc9000.stable") => "/_matrix/media/r0/qux",
121///         1.1 => "/_matrix/media/v3/qux",
122///         1.2 => deprecated,
123///         1.3 => removed,
124///     }
125/// };
126///
127/// /// A request with a single path.
128/// pub struct MySinglePathRequest {
129///     body: Vec<u8>,
130/// }
131///
132/// metadata! {
133///     @for MySinglePathRequest,
134///
135///     method: GET,
136///     rate_limited: false,
137///     authentication: NoAuthentication,
138///     path: "/_matrix/key/query",
139/// };
140/// ```
141#[doc(hidden)]
142#[macro_export]
143macro_rules! metadata {
144    ( @for $request_type:ty, $( $field:ident: $rhs:tt ),+ $(,)? ) => {
145        #[allow(deprecated)]
146        impl $crate::api::Metadata for $request_type {
147            $( $crate::metadata!(@field $field: $rhs); )+
148        }
149    };
150
151    ( $( $field:ident: $rhs:tt ),+ $(,)? ) => {
152        $crate::metadata!{ @for Request, $( $field: $rhs),+ }
153    };
154
155    ( @field method: $method:ident ) => {
156        const METHOD: $crate::exports::http::Method = $crate::exports::http::Method::$method;
157    };
158
159    ( @field rate_limited: $rate_limited:literal ) => { const RATE_LIMITED: bool = $rate_limited; };
160
161    ( @field required_client_scopes: [$( $scope:expr ),+ $(,)?] ) => {
162        fn required_client_scopes() -> &'static [$crate::api::OAuthClientScope]
163        where
164            Self::Authentication: $crate::api::auth_scheme::ClientScopedAuthScheme
165        {
166            &[$($scope,)+]
167        }
168    };
169
170    ( @field authentication: $scheme:path ) => {
171        type Authentication = $scheme;
172    };
173
174    ( @field path: $path:literal ) => {
175        type PathBuilder = $crate::api::path_builder::SinglePath;
176        const PATH_BUILDER: $crate::api::path_builder::SinglePath = $crate::api::path_builder::SinglePath::new($path);
177    };
178
179    ( @field history: {
180        $( unstable $(($unstable_feature:literal))? => $unstable_path:literal, )*
181        $( stable ($stable_feature_only:literal) => $stable_feature_path:literal, )*
182        $( $version:literal $(| stable ($stable_feature:literal))? => $stable_rhs:tt, )*
183    } ) => {
184        $crate::metadata! {
185            @history_impl
186            $( unstable $( ($unstable_feature) )? => $unstable_path, )*
187            $( stable ($stable_feature_only) => $stable_feature_path, )*
188            // Flip left and right to avoid macro parsing ambiguities
189            $( $stable_rhs = $version $( | stable ($stable_feature) )?, )*
190        }
191    };
192
193    ( @history_impl
194        $( unstable $(($unstable_feature:literal))? => $unstable_path:literal, )*
195        $( stable ($stable_feature_only:literal) => $stable_feature_path:literal, )*
196        $( $stable_path:literal = $version:literal $(| stable ($stable_feature:literal))?, )*
197        $( deprecated = $deprecated_version:literal, )?
198        $( removed = $removed_version:literal, )?
199    ) => {
200        type PathBuilder = $crate::api::path_builder::VersionHistory;
201        const PATH_BUILDER: $crate::api::path_builder::VersionHistory = $crate::api::path_builder::VersionHistory::new(
202            &[ $(($crate::metadata!(@optional_feature $($unstable_feature)?), $unstable_path)),* ],
203            &[
204                $((
205                    $crate::metadata!(@stable_path_selector stable($stable_feature_only)),
206                    $stable_feature_path
207                ),)*
208                $((
209                    $crate::metadata!(@stable_path_selector $version $( | stable($stable_feature) )?),
210                    $stable_path
211                ),)*
212            ],
213            $crate::metadata!(@optional_version $( $deprecated_version )?),
214            $crate::metadata!(@optional_version $( $removed_version )?),
215        );
216    };
217
218    ( @optional_feature ) => { None };
219    ( @optional_feature $feature:literal ) => { Some($feature) };
220    ( @stable_path_selector stable($feature:literal)) => {
221        $crate::api::path_builder::StablePathSelector::Feature($feature)
222    };
223    ( @stable_path_selector $version:literal | stable($feature:literal)) => {
224        $crate::api::path_builder::StablePathSelector::FeatureAndVersion {
225            feature: $feature,
226            version: $crate::api::MatrixVersion::from_lit(stringify!($version)),
227        }
228    };
229    ( @stable_path_selector $version:literal) => {
230        $crate::api::path_builder::StablePathSelector::Version(
231            $crate::api::MatrixVersion::from_lit(stringify!($version))
232        )
233    };
234    ( @optional_version ) => { None };
235    ( @optional_version $version:literal ) => { Some($crate::api::MatrixVersion::from_lit(stringify!($version))) }
236}
237
238/// Metadata about an API endpoint.
239pub trait Metadata: Sized {
240    /// The HTTP method used by this endpoint.
241    const METHOD: Method;
242
243    /// Whether or not this endpoint is rate limited by the server.
244    const RATE_LIMITED: bool;
245
246    /// What authentication scheme the server uses for this endpoint.
247    type Authentication: AuthScheme;
248
249    /// The type used to build an endpoint's path.
250    type PathBuilder: PathBuilder;
251
252    /// All info pertaining to an endpoint's path.
253    const PATH_BUILDER: Self::PathBuilder;
254
255    /// Generate the endpoint URL for this endpoint.
256    fn make_endpoint_url(
257        path_builder_input: <Self::PathBuilder as PathBuilder>::Input<'_>,
258        base_url: &str,
259        path_args: &[&dyn Display],
260        query_string: &str,
261    ) -> Result<String, IntoHttpError> {
262        Self::PATH_BUILDER.make_endpoint_url(path_builder_input, base_url, path_args, query_string)
263    }
264
265    /// The OAuth scopes which grant access to this endpoint.
266    ///
267    /// Clients which authenticated using the [OAuth 2.0 API] may only use this endpoint
268    /// if they requested _any one_ of the scopes in the returned slice. For most endpoints,
269    /// this is only [`OAuthClientScope::ApiFullAccess`].
270    ///
271    /// This function is only defined for request structs with an authentication scheme
272    /// that implements the [`ClientScopedAuthScheme`] marker trait.
273    ///
274    /// [OAuth 2.0 API]: https://spec.matrix.org/v1.19/client-server-api/#oauth-20-api
275    fn required_client_scopes() -> &'static [OAuthClientScope]
276    where
277        Self::Authentication: ClientScopedAuthScheme,
278    {
279        // TODO: In the future this could possibly be converted into a generic associated constant
280        // once those are stabilized. See https://github.com/rust-lang/rust/issues/113521.
281        &[OAuthClientScope::ApiFullAccess]
282    }
283
284    /// The list of path parameters in the metadata.
285    ///
286    /// Used for `#[test]`s generated by the API macros.
287    #[doc(hidden)]
288    fn _path_parameters() -> Vec<&'static str> {
289        Self::PATH_BUILDER._path_parameters()
290    }
291}
292
293/// The Matrix versions Ruma currently understands to exist.
294///
295/// Matrix, since fall 2021, has a quarterly release schedule, using a global `vX.Y` versioning
296/// scheme. Usually `Y` is bumped for new backwards compatible changes, but `X` can be bumped
297/// instead when a large number of `Y` changes feel deserving of a major version increase.
298///
299/// Every new version denotes stable support for endpoints in a *relatively* backwards-compatible
300/// manner.
301///
302/// Matrix has a deprecation policy, read more about it here: <https://spec.matrix.org/v1.19/#deprecation-policy>.
303///
304/// Ruma keeps track of when endpoints are added, deprecated, and removed. It'll automatically
305/// select the right endpoint stability variation to use depending on which Matrix versions you
306/// pass to [`try_into_http_request`](super::OutgoingRequestExt::try_into_http_request), see its
307/// respective documentation for more information.
308///
309/// The `PartialOrd` and `Ord` implementations of this type sort the variants by release date. A
310/// newer release is greater than an older release.
311///
312/// `MatrixVersion::is_superset_of()` is used to keep track of compatibility between versions.
313#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
314#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
315pub enum MatrixVersion {
316    /// Matrix 1.0 was a release prior to the global versioning system and does not correspond to a
317    /// version of the Matrix specification.
318    ///
319    /// It matches the following per-API versions:
320    ///
321    /// * Client-Server API: r0.5.0 to r0.6.1
322    /// * Identity Service API: r0.2.0 to r0.3.0
323    ///
324    /// The other APIs are not supported because they do not have a `GET /versions` endpoint.
325    ///
326    /// See <https://spec.matrix.org/v1.19/#legacy-versioning>.
327    V1_0,
328
329    /// Version 1.1 of the Matrix specification, released in Q4 2021.
330    ///
331    /// See <https://spec.matrix.org/v1.1/>.
332    V1_1,
333
334    /// Version 1.2 of the Matrix specification, released in Q1 2022.
335    ///
336    /// See <https://spec.matrix.org/v1.2/>.
337    V1_2,
338
339    /// Version 1.3 of the Matrix specification, released in Q2 2022.
340    ///
341    /// See <https://spec.matrix.org/v1.3/>.
342    V1_3,
343
344    /// Version 1.4 of the Matrix specification, released in Q3 2022.
345    ///
346    /// See <https://spec.matrix.org/v1.4/>.
347    V1_4,
348
349    /// Version 1.5 of the Matrix specification, released in Q4 2022.
350    ///
351    /// See <https://spec.matrix.org/v1.5/>.
352    V1_5,
353
354    /// Version 1.6 of the Matrix specification, released in Q1 2023.
355    ///
356    /// See <https://spec.matrix.org/v1.6/>.
357    V1_6,
358
359    /// Version 1.7 of the Matrix specification, released in Q2 2023.
360    ///
361    /// See <https://spec.matrix.org/v1.7/>.
362    V1_7,
363
364    /// Version 1.8 of the Matrix specification, released in Q3 2023.
365    ///
366    /// See <https://spec.matrix.org/v1.8/>.
367    V1_8,
368
369    /// Version 1.9 of the Matrix specification, released in Q4 2023.
370    ///
371    /// See <https://spec.matrix.org/v1.9/>.
372    V1_9,
373
374    /// Version 1.10 of the Matrix specification, released in Q1 2024.
375    ///
376    /// See <https://spec.matrix.org/v1.10/>.
377    V1_10,
378
379    /// Version 1.11 of the Matrix specification, released in Q2 2024.
380    ///
381    /// See <https://spec.matrix.org/v1.11/>.
382    V1_11,
383
384    /// Version 1.12 of the Matrix specification, released in Q3 2024.
385    ///
386    /// See <https://spec.matrix.org/v1.12/>.
387    V1_12,
388
389    /// Version 1.13 of the Matrix specification, released in Q4 2024.
390    ///
391    /// See <https://spec.matrix.org/v1.13/>.
392    V1_13,
393
394    /// Version 1.14 of the Matrix specification, released in Q1 2025.
395    ///
396    /// See <https://spec.matrix.org/v1.14/>.
397    V1_14,
398
399    /// Version 1.15 of the Matrix specification, released in Q2 2025.
400    ///
401    /// See <https://spec.matrix.org/v1.15/>.
402    V1_15,
403
404    /// Version 1.16 of the Matrix specification, released in Q3 2025.
405    ///
406    /// See <https://spec.matrix.org/v1.16/>.
407    V1_16,
408
409    /// Version 1.17 of the Matrix specification, released in Q4 2025.
410    ///
411    /// See <https://spec.matrix.org/v1.17/>.
412    V1_17,
413
414    /// Version 1.18 of the Matrix specification, released in Q1 2026.
415    ///
416    /// See <https://spec.matrix.org/v1.18/>.
417    V1_18,
418
419    /// Version 1.19 of the Matrix specification, released in Q2 2026.
420    ///
421    /// See <https://spec.matrix.org/v1.19/>.
422    V1_19,
423}
424
425impl TryFrom<&str> for MatrixVersion {
426    type Error = UnknownVersionError;
427
428    fn try_from(value: &str) -> Result<MatrixVersion, Self::Error> {
429        use MatrixVersion::*;
430
431        Ok(match value {
432            // Identity service API versions between Matrix 1.0 and 1.1.
433            // They might match older client-server API versions but that should not be a problem in practice.
434            "r0.2.0" | "r0.2.1" | "r0.3.0" |
435            // Client-server API versions between Matrix 1.0 and 1.1.
436            "r0.5.0" | "r0.6.0" | "r0.6.1" => V1_0,
437            "v1.1" => V1_1,
438            "v1.2" => V1_2,
439            "v1.3" => V1_3,
440            "v1.4" => V1_4,
441            "v1.5" => V1_5,
442            "v1.6" => V1_6,
443            "v1.7" => V1_7,
444            "v1.8" => V1_8,
445            "v1.9" => V1_9,
446            "v1.10" => V1_10,
447            "v1.11" => V1_11,
448            "v1.12" => V1_12,
449            "v1.13" => V1_13,
450            "v1.14" => V1_14,
451            "v1.15" => V1_15,
452            "v1.16" => V1_16,
453            "v1.17" => V1_17,
454            "v1.18" => V1_18,
455            "v1.19" => V1_19,
456            _ => return Err(UnknownVersionError),
457        })
458    }
459}
460
461impl FromStr for MatrixVersion {
462    type Err = UnknownVersionError;
463
464    fn from_str(s: &str) -> Result<Self, Self::Err> {
465        Self::try_from(s)
466    }
467}
468
469impl MatrixVersion {
470    /// Checks whether a version is compatible with another.
471    ///
472    /// Currently, all versions of Matrix are considered backwards compatible with all the previous
473    /// versions, so this is equivalent to `self >= other`. This behaviour may change in the future,
474    /// if a new release is considered to be breaking compatibility with the previous ones.
475    ///
476    /// > ⚠ Matrix has a deprecation policy, and Matrix versioning is not as straightforward as this
477    /// > function makes it out to be. This function only exists to prune breaking changes between
478    /// > versions, and versions too new for `self`.
479    pub fn is_superset_of(self, other: Self) -> bool {
480        self >= other
481    }
482
483    /// Get a string representation of this Matrix version.
484    ///
485    /// This is the string that can be found in the response to one of the `GET /versions`
486    /// endpoints. Parsing this string will give the same variant.
487    ///
488    /// Returns `None` for [`MatrixVersion::V1_0`] because it can match several per-API versions.
489    pub const fn as_str(self) -> Option<&'static str> {
490        let string = match self {
491            MatrixVersion::V1_0 => return None,
492            MatrixVersion::V1_1 => "v1.1",
493            MatrixVersion::V1_2 => "v1.2",
494            MatrixVersion::V1_3 => "v1.3",
495            MatrixVersion::V1_4 => "v1.4",
496            MatrixVersion::V1_5 => "v1.5",
497            MatrixVersion::V1_6 => "v1.6",
498            MatrixVersion::V1_7 => "v1.7",
499            MatrixVersion::V1_8 => "v1.8",
500            MatrixVersion::V1_9 => "v1.9",
501            MatrixVersion::V1_10 => "v1.10",
502            MatrixVersion::V1_11 => "v1.11",
503            MatrixVersion::V1_12 => "v1.12",
504            MatrixVersion::V1_13 => "v1.13",
505            MatrixVersion::V1_14 => "v1.14",
506            MatrixVersion::V1_15 => "v1.15",
507            MatrixVersion::V1_16 => "v1.16",
508            MatrixVersion::V1_17 => "v1.17",
509            MatrixVersion::V1_18 => "v1.18",
510            MatrixVersion::V1_19 => "v1.19",
511        };
512
513        Some(string)
514    }
515
516    /// Decompose the Matrix version into its major and minor number.
517    const fn into_parts(self) -> (u8, u8) {
518        match self {
519            MatrixVersion::V1_0 => (1, 0),
520            MatrixVersion::V1_1 => (1, 1),
521            MatrixVersion::V1_2 => (1, 2),
522            MatrixVersion::V1_3 => (1, 3),
523            MatrixVersion::V1_4 => (1, 4),
524            MatrixVersion::V1_5 => (1, 5),
525            MatrixVersion::V1_6 => (1, 6),
526            MatrixVersion::V1_7 => (1, 7),
527            MatrixVersion::V1_8 => (1, 8),
528            MatrixVersion::V1_9 => (1, 9),
529            MatrixVersion::V1_10 => (1, 10),
530            MatrixVersion::V1_11 => (1, 11),
531            MatrixVersion::V1_12 => (1, 12),
532            MatrixVersion::V1_13 => (1, 13),
533            MatrixVersion::V1_14 => (1, 14),
534            MatrixVersion::V1_15 => (1, 15),
535            MatrixVersion::V1_16 => (1, 16),
536            MatrixVersion::V1_17 => (1, 17),
537            MatrixVersion::V1_18 => (1, 18),
538            MatrixVersion::V1_19 => (1, 19),
539        }
540    }
541
542    /// Try to turn a pair of (major, minor) version components back into a `MatrixVersion`.
543    const fn from_parts(major: u8, minor: u8) -> Result<Self, UnknownVersionError> {
544        match (major, minor) {
545            (1, 0) => Ok(MatrixVersion::V1_0),
546            (1, 1) => Ok(MatrixVersion::V1_1),
547            (1, 2) => Ok(MatrixVersion::V1_2),
548            (1, 3) => Ok(MatrixVersion::V1_3),
549            (1, 4) => Ok(MatrixVersion::V1_4),
550            (1, 5) => Ok(MatrixVersion::V1_5),
551            (1, 6) => Ok(MatrixVersion::V1_6),
552            (1, 7) => Ok(MatrixVersion::V1_7),
553            (1, 8) => Ok(MatrixVersion::V1_8),
554            (1, 9) => Ok(MatrixVersion::V1_9),
555            (1, 10) => Ok(MatrixVersion::V1_10),
556            (1, 11) => Ok(MatrixVersion::V1_11),
557            (1, 12) => Ok(MatrixVersion::V1_12),
558            (1, 13) => Ok(MatrixVersion::V1_13),
559            (1, 14) => Ok(MatrixVersion::V1_14),
560            (1, 15) => Ok(MatrixVersion::V1_15),
561            (1, 16) => Ok(MatrixVersion::V1_16),
562            (1, 17) => Ok(MatrixVersion::V1_17),
563            (1, 18) => Ok(MatrixVersion::V1_18),
564            (1, 19) => Ok(MatrixVersion::V1_19),
565            _ => Err(UnknownVersionError),
566        }
567    }
568
569    /// Constructor for use by the `metadata!` macro.
570    ///
571    /// Accepts string literals and parses them.
572    #[doc(hidden)]
573    pub const fn from_lit(lit: &'static str) -> Self {
574        use konst::{result, string};
575
576        let mut lit_parts = string::split(lit, ".");
577
578        let checked_first = lit_parts.next().unwrap(); // First iteration always succeeds
579        let major = result::unwrap_or_else!(u8::from_str_radix(checked_first, 10), |_| panic!(
580            "major version is not a valid number"
581        ));
582
583        let Some(checked_second) = lit_parts.next() else {
584            panic!("could not find dot to denote second number");
585        };
586        let minor = result::unwrap_or_else!(u8::from_str_radix(checked_second, 10), |_| panic!(
587            "minor version is not a valid number"
588        ));
589
590        if lit_parts.next().is_some() {
591            panic!("version literal contains more than one dot")
592        }
593
594        result::unwrap_or_else!(Self::from_parts(major, minor), |_| panic!(
595            "not a valid version literal"
596        ))
597    }
598
599    // Internal function to do ordering in const-fn contexts
600    pub(super) const fn const_ord(&self, other: &Self) -> Ordering {
601        let self_parts = self.into_parts();
602        let other_parts = other.into_parts();
603
604        use konst::primitive::cmp::cmp_u8;
605
606        let major_ord = cmp_u8(self_parts.0, other_parts.0);
607        if major_ord.is_ne() { major_ord } else { cmp_u8(self_parts.1, other_parts.1) }
608    }
609
610    // Internal function to check if this version is the legacy (v1.0) version in const-fn contexts
611    pub(super) const fn is_legacy(&self) -> bool {
612        let self_parts = self.into_parts();
613
614        use konst::primitive::cmp::cmp_u8;
615
616        cmp_u8(self_parts.0, 1).is_eq() && cmp_u8(self_parts.1, 0).is_eq()
617    }
618
619    /// Get the default [`RoomVersionId`] for this `MatrixVersion`.
620    pub fn default_room_version(&self) -> RoomVersionId {
621        match self {
622            // <https://spec.matrix.org/historical/index.html#complete-list-of-room-versions>
623            MatrixVersion::V1_0
624            // <https://spec.matrix.org/v1.1/rooms/#complete-list-of-room-versions>
625            | MatrixVersion::V1_1
626            // <https://spec.matrix.org/v1.2/rooms/#complete-list-of-room-versions>
627            | MatrixVersion::V1_2 => RoomVersionId::V6,
628            // <https://spec.matrix.org/v1.3/rooms/#complete-list-of-room-versions>
629            MatrixVersion::V1_3
630            // <https://spec.matrix.org/v1.4/rooms/#complete-list-of-room-versions>
631            | MatrixVersion::V1_4
632            // <https://spec.matrix.org/v1.5/rooms/#complete-list-of-room-versions>
633            | MatrixVersion::V1_5 => RoomVersionId::V9,
634            // <https://spec.matrix.org/v1.6/rooms/#complete-list-of-room-versions>
635            MatrixVersion::V1_6
636            // <https://spec.matrix.org/v1.7/rooms/#complete-list-of-room-versions>
637            | MatrixVersion::V1_7
638            // <https://spec.matrix.org/v1.8/rooms/#complete-list-of-room-versions>
639            | MatrixVersion::V1_8
640            // <https://spec.matrix.org/v1.9/rooms/#complete-list-of-room-versions>
641            | MatrixVersion::V1_9
642            // <https://spec.matrix.org/v1.10/rooms/#complete-list-of-room-versions>
643            | MatrixVersion::V1_10
644            // <https://spec.matrix.org/v1.11/rooms/#complete-list-of-room-versions>
645            | MatrixVersion::V1_11
646            // <https://spec.matrix.org/v1.12/rooms/#complete-list-of-room-versions>
647            | MatrixVersion::V1_12
648            // <https://spec.matrix.org/v1.13/rooms/#complete-list-of-room-versions>
649            | MatrixVersion::V1_13 => RoomVersionId::V10,
650            // <https://spec.matrix.org/v1.14/rooms/#complete-list-of-room-versions>
651            | MatrixVersion::V1_14
652            // <https://spec.matrix.org/v1.15/rooms/#complete-list-of-room-versions>
653            | MatrixVersion::V1_15 => RoomVersionId::V11,
654            // <https://spec.matrix.org/v1.16/rooms/#complete-list-of-room-versions>
655            MatrixVersion::V1_16
656            // <https://spec.matrix.org/v1.17/rooms/#complete-list-of-room-versions>
657            | MatrixVersion::V1_17
658            // <https://spec.matrix.org/v1.18/rooms/#complete-list-of-room-versions>
659            | MatrixVersion::V1_18
660            // <https://spec.matrix.org/v1.19/rooms/#complete-list-of-room-versions>
661            | MatrixVersion::V1_19 => RoomVersionId::V12,
662        }
663    }
664}
665
666/// The list of Matrix versions and features supported by a homeserver.
667#[derive(Debug, Clone)]
668#[allow(clippy::exhaustive_structs)]
669pub struct SupportedVersions {
670    /// The Matrix versions that are supported by the homeserver.
671    ///
672    /// This set contains only known versions.
673    pub versions: BTreeSet<MatrixVersion>,
674
675    /// The features that are supported by the homeserver.
676    ///
677    /// This matches the `unstable_features` field of the `/versions` endpoint, without the boolean
678    /// value.
679    pub features: BTreeSet<FeatureFlag>,
680}
681
682impl SupportedVersions {
683    /// Construct a `SupportedVersions` from the parts of a `/versions` response.
684    ///
685    /// Matrix versions that can't be parsed to a `MatrixVersion`, and features with the boolean
686    /// value set to `false` are discarded.
687    pub fn from_parts(versions: &[String], unstable_features: &BTreeMap<String, bool>) -> Self {
688        Self {
689            versions: versions.iter().flat_map(|s| s.parse::<MatrixVersion>()).collect(),
690            features: unstable_features
691                .iter()
692                .filter(|(_, enabled)| **enabled)
693                .map(|(feature, _)| feature.as_str().into())
694                .collect(),
695        }
696    }
697}
698
699/// The Matrix features supported by Ruma.
700///
701/// Features that are not behind a cargo feature are features that are part of the Matrix
702/// specification and that Ruma still supports, like the unstable version of an endpoint or a stable
703/// feature. Features behind a cargo feature are only supported when this feature is enabled.
704#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
705#[derive(Clone, StringEnum, Hash)]
706#[non_exhaustive]
707pub enum FeatureFlag {
708    /// `fi.mau.msc2246` ([MSC])
709    ///
710    /// Asynchronous media uploads.
711    ///
712    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/2246
713    #[ruma_enum(rename = "fi.mau.msc2246")]
714    Msc2246,
715
716    /// `org.matrix.msc2432` ([MSC])
717    ///
718    /// Updated semantics for publishing room aliases.
719    ///
720    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/2432
721    #[ruma_enum(rename = "org.matrix.msc2432")]
722    Msc2432,
723
724    /// `fi.mau.msc2659` ([MSC])
725    ///
726    /// Application service ping endpoint.
727    ///
728    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/2659
729    #[ruma_enum(rename = "fi.mau.msc2659")]
730    Msc2659,
731
732    /// `fi.mau.msc2659` ([MSC])
733    ///
734    /// Stable version of the application service ping endpoint.
735    ///
736    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/2659
737    #[ruma_enum(rename = "fi.mau.msc2659.stable")]
738    Msc2659Stable,
739
740    /// `uk.half-shot.msc2666.query_mutual_rooms` ([MSC])
741    ///
742    /// Get rooms in common with another user.
743    ///
744    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/2666
745    #[ruma_enum(rename = "uk.half-shot.msc2666.query_mutual_rooms")]
746    Msc2666,
747
748    /// `uk.half-shot.msc2666.query_mutual_rooms.stable` ([MSC])
749    ///
750    /// Get rooms in common with another user. (stable version)
751    ///
752    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/2666
753    #[ruma_enum(rename = "uk.half-shot.msc2666.query_mutual_rooms.stable")]
754    Msc2666Stable,
755
756    /// `org.matrix.msc3030` ([MSC])
757    ///
758    /// Jump to date API endpoint.
759    ///
760    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3030
761    #[ruma_enum(rename = "org.matrix.msc3030")]
762    Msc3030,
763
764    /// `org.matrix.msc3882` ([MSC])
765    ///
766    /// Allow an existing session to sign in a new session.
767    ///
768    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3882
769    #[ruma_enum(rename = "org.matrix.msc3882")]
770    Msc3882,
771
772    /// `org.matrix.msc3916` ([MSC])
773    ///
774    /// Authentication for media.
775    ///
776    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3916
777    #[ruma_enum(rename = "org.matrix.msc3916")]
778    Msc3916,
779
780    /// `org.matrix.msc3916.stable` ([MSC])
781    ///
782    /// Stable version of authentication for media.
783    ///
784    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3916
785    #[ruma_enum(rename = "org.matrix.msc3916.stable")]
786    Msc3916Stable,
787
788    /// `org.matrix.msc4108` ([MSC])
789    ///
790    /// Mechanism to allow OIDC sign in and E2EE set up via QR code.
791    ///
792    /// This is for the unstable 2024 version of the [MSC].
793    ///
794    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
795    #[cfg(feature = "unstable-msc4108")]
796    #[ruma_enum(rename = "org.matrix.msc4108")]
797    Msc4108,
798
799    /// `org.matrix.msc4140` ([MSC])
800    ///
801    /// Delayed events.
802    ///
803    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4140
804    #[cfg(feature = "unstable-msc4140")]
805    #[ruma_enum(rename = "org.matrix.msc4140")]
806    Msc4140,
807
808    /// `org.matrix.simplified_msc3575` ([MSC])
809    ///
810    /// Simplified Sliding Sync.
811    ///
812    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4186
813    #[cfg(feature = "unstable-msc4186")]
814    #[ruma_enum(rename = "org.matrix.simplified_msc3575")]
815    Msc4186,
816
817    /// `uk.timedout.msc4323` ([MSC])
818    ///
819    /// Suspend and lock endpoints.
820    ///
821    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323
822    #[ruma_enum(rename = "uk.timedout.msc4323")]
823    Msc4323,
824
825    /// `org.matrix.msc4380_invite_permission_config` ([MSC])
826    ///
827    /// Invite Blocking.
828    ///
829    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4380
830    #[ruma_enum(rename = "org.matrix.msc4380")]
831    Msc4380,
832
833    /// `org.continuwuity.msc4484.unstable` ([MSC])
834    ///
835    /// Server administration OAuth scope.
836    ///
837    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4484
838    #[ruma_enum(rename = "org.continuwuity.msc4484.unstable")]
839    Msc4484,
840
841    /// `org.continuwuity.presence_v2.msc4495` ([MSC])
842    ///
843    /// Selective Presence.
844    ///
845    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4495
846    #[cfg(feature = "unstable-msc4495")]
847    #[ruma_enum(rename = "org.continuwuity.presence_v2.msc4495")]
848    Msc4495,
849
850    /// `uk.timedout.msc4494` ([MSC])
851    ///
852    /// Membership-based invite blocking
853    ///
854    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/4494
855    #[cfg(feature = "unstable-msc4494")]
856    #[ruma_enum(rename = "uk.timedout.msc4494")]
857    Msc4494,
858
859    #[doc(hidden)]
860    _Custom(PrivOwnedStr),
861}
862
863/// An OAuth scope which grants access to some client-server API endpoints.
864///
865/// This enum does _not_ include the [device ID scope], which isn't really a scope (as it doesn't
866/// grant access to anything) but instead a way to reserve a specific device ID.
867///
868/// [device ID scope]: https://spec.matrix.org/v1.19/client-server-api/#device-id-allocation
869#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
870#[derive(Clone, StringEnum, Hash)]
871#[non_exhaustive]
872pub enum OAuthClientScope {
873    /// Full access to all endpoints of the client-server API, unless explicitly noted.
874    #[ruma_enum(
875        rename = "urn:matrix:client:api:*",
876        alias = "urn:matrix:org.matrix.msc2967.client:api:*"
877    )]
878    ApiFullAccess,
879
880    /// Access to the endpoints in the [Server Administration] module.
881    ///
882    /// [Server Administration]: https://spec.matrix.org/v1.19/client-server-api/#server-administration
883    #[cfg(feature = "unstable-msc4484")]
884    #[ruma_enum(rename = "urn:matrix:client:cc.c10y.msc4484.server_administration")]
885    ServerAdministration,
886
887    #[doc(hidden)]
888    _Custom(PrivOwnedStr),
889}