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