api: Add Metadata::versioning_decision_for

This commit is contained in:
Jonathan de Jong 2022-04-11 15:44:03 +02:00 committed by GitHub
parent 873270db1d
commit f0177dc429
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 104 additions and 46 deletions

View File

@ -15,6 +15,7 @@
use std::{convert::TryInto as _, error::Error as StdError, fmt}; use std::{convert::TryInto as _, error::Error as StdError, fmt};
use bytes::BufMut; use bytes::BufMut;
use tracing::warn;
use crate::UserId; use crate::UserId;
@ -195,7 +196,7 @@ pub use ruma_macros::ruma_api;
pub mod error; pub mod error;
mod metadata; mod metadata;
pub use metadata::{MatrixVersion, Metadata}; pub use metadata::{MatrixVersion, Metadata, VersioningDecision};
use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError}; use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError};
@ -401,10 +402,6 @@ pub enum AuthScheme {
// This function helps picks the right path (or an error) from a set of matrix versions. // This function helps picks the right path (or an error) from a set of matrix versions.
// //
// This function needs to be public, yet hidden, as all `try_into_http_request`s would be using it. // This function needs to be public, yet hidden, as all `try_into_http_request`s would be using it.
//
// Note this assumes that `versions`;
// - at least has 1 element
// - have all elements sorted by its `.into_parts` representation.
#[doc(hidden)] #[doc(hidden)]
pub fn select_path<'a>( pub fn select_path<'a>(
versions: &'_ [MatrixVersion], versions: &'_ [MatrixVersion],
@ -413,50 +410,48 @@ pub fn select_path<'a>(
r0: Option<fmt::Arguments<'a>>, r0: Option<fmt::Arguments<'a>>,
stable: Option<fmt::Arguments<'a>>, stable: Option<fmt::Arguments<'a>>,
) -> Result<fmt::Arguments<'a>, IntoHttpError> { ) -> Result<fmt::Arguments<'a>, IntoHttpError> {
let greater_or_equal_any = match metadata.versioning_decision_for(versions) {
|version: MatrixVersion| versions.iter().any(|v| v.is_superset_of(version)); VersioningDecision::Removed => Err(IntoHttpError::EndpointRemoved(
let greater_or_equal_all = metadata.removed.expect("VersioningDecision::Removed implies metadata.removed"),
|version: MatrixVersion| versions.iter().all(|v| v.is_superset_of(version)); )),
VersioningDecision::Stable { any_deprecated, all_deprecated, any_removed } => {
let is_stable_any = metadata.added.map(greater_or_equal_any).unwrap_or(false); if any_removed {
if all_deprecated {
let is_removed_all = metadata.removed.map(greater_or_equal_all).unwrap_or(false); warn!(
"endpoint {} is removed in some (and deprecated in ALL) of the following versions: {:?}",
// Only when all the versions (e.g. 1.6-9) are compatible with the version that added it (e.g. metadata.name,
// 1.1), yet are also "after" the version that removed it (e.g. 1.3), then we return that error. versions
// Otherwise, this all versions may fall into a different major range, such as 2.X, where );
// "after" and "compatible" do not exist with the 1.X range, so we at least need to make sure } else if any_deprecated {
// that the versions are part of the same "range" through the `added` check. warn!(
if is_stable_any && is_removed_all { "endpoint {} is removed (and deprecated) in some of the following versions: {:?}",
return Err(IntoHttpError::EndpointRemoved(metadata.removed.unwrap())); metadata.name,
} versions
);
if is_stable_any { } else {
let is_deprecated_any = metadata.deprecated.map(greater_or_equal_any).unwrap_or(false); unreachable!("any_removed implies *_deprecated");
}
if is_deprecated_any { } else if all_deprecated {
let is_removed_any = metadata.removed.map(greater_or_equal_any).unwrap_or(false); warn!(
"endpoint {} is deprecated in ALL of the following versions: {:?}",
if is_removed_any { metadata.name, versions
tracing::warn!("endpoint {} is deprecated, but also removed in one of more server-passed versions: {:?}", metadata.name, versions) );
} else { } else if any_deprecated {
tracing::warn!( warn!(
"endpoint {} is deprecated in one of more server-passed versions: {:?}", "endpoint {} is deprecated in some of the following versions: {:?}",
metadata.name, metadata.name, versions
versions );
)
} }
}
if let Some(r0) = r0 { if let Some(r0) = r0 {
if versions.iter().all(|&v| v == MatrixVersion::V1_0) { if versions.iter().all(|&v| v == MatrixVersion::V1_0) {
// Endpoint was added in 1.0, we return the r0 variant. // Endpoint was added in 1.0, we return the r0 variant.
return Ok(r0); return Ok(r0);
}
} }
Ok(stable.expect("metadata.added enforces the stable path to exist"))
} }
VersioningDecision::Unstable => unstable.ok_or(IntoHttpError::NoUnstablePath),
return Ok(stable.expect("metadata.added enforces the stable path to exist"));
} }
unstable.ok_or(IntoHttpError::NoUnstablePath)
} }

View File

@ -59,6 +59,69 @@ pub struct Metadata {
pub removed: Option<MatrixVersion>, pub removed: Option<MatrixVersion>,
} }
impl Metadata {
/// Will decide how a particular set of matrix versions sees an endpoint.
///
/// It will pick `Stable` over `R0` and `Unstable`. It'll return `Deprecated` or `Removed` only
/// if all versions denote it.
///
/// In other words, if in any version it tells it supports the endpoint in a stable fashion,
/// this will return `Stable`, even if some versions in this set will denote deprecation or
/// removal.
///
/// If resulting [`VersioningDecision`] is `Stable`, it will also detail if any version denoted
/// deprecation or removal.
pub fn versioning_decision_for(&self, versions: &[MatrixVersion]) -> VersioningDecision {
let greater_or_equal_any =
|version: MatrixVersion| versions.iter().any(|v| v.is_superset_of(version));
let greater_or_equal_all =
|version: MatrixVersion| versions.iter().all(|v| v.is_superset_of(version));
// Check if all versions removed this endpoint.
if self.removed.map(greater_or_equal_all).unwrap_or(false) {
return VersioningDecision::Removed;
}
// Check if *any* version marks this endpoint as stable.
if self.added.map(greater_or_equal_any).unwrap_or(false) {
let all_deprecated = self.deprecated.map(greater_or_equal_all).unwrap_or(false);
return VersioningDecision::Stable {
any_deprecated: all_deprecated
|| self.deprecated.map(greater_or_equal_any).unwrap_or(false),
all_deprecated,
any_removed: self.removed.map(greater_or_equal_any).unwrap_or(false),
};
}
VersioningDecision::Unstable
}
}
/// A versioning "decision" derived from a set of matrix versions.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[allow(clippy::exhaustive_enums)]
pub enum VersioningDecision {
/// The unstable endpoint should be used.
Unstable,
/// The stable endpoint should be used.
///
/// Note, in the special case that all versions note [v1.0](MatrixVersion::V1_0), and the
/// [`r0_path`](Metadata::r0_path) is not `None`, that path should be used.
Stable {
/// If any version denoted deprecation.
any_deprecated: bool,
/// If *all* versions denoted deprecation.
all_deprecated: bool,
/// If any version denoted removal.
any_removed: bool,
},
/// This endpoint was removed in all versions, it should not be used.
Removed,
}
/// The Matrix versions Ruma currently understands to exist. /// The Matrix versions Ruma currently understands to exist.
/// ///
/// Matrix, since fall 2021, has a quarterly release schedule, using a global `vX.Y` versioning /// Matrix, since fall 2021, has a quarterly release schedule, using a global `vX.Y` versioning