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 bytes::BufMut;
use tracing::warn;
use crate::UserId;
@ -195,7 +196,7 @@ pub use ruma_macros::ruma_api;
pub mod error;
mod metadata;
pub use metadata::{MatrixVersion, Metadata};
pub use metadata::{MatrixVersion, Metadata, VersioningDecision};
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 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)]
pub fn select_path<'a>(
versions: &'_ [MatrixVersion],
@ -413,50 +410,48 @@ pub fn select_path<'a>(
r0: Option<fmt::Arguments<'a>>,
stable: Option<fmt::Arguments<'a>>,
) -> Result<fmt::Arguments<'a>, IntoHttpError> {
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));
let is_stable_any = metadata.added.map(greater_or_equal_any).unwrap_or(false);
let is_removed_all = metadata.removed.map(greater_or_equal_all).unwrap_or(false);
// Only when all the versions (e.g. 1.6-9) are compatible with the version that added it (e.g.
// 1.1), yet are also "after" the version that removed it (e.g. 1.3), then we return that error.
// 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
// that the versions are part of the same "range" through the `added` check.
if is_stable_any && is_removed_all {
return Err(IntoHttpError::EndpointRemoved(metadata.removed.unwrap()));
}
if is_stable_any {
let is_deprecated_any = metadata.deprecated.map(greater_or_equal_any).unwrap_or(false);
if is_deprecated_any {
let is_removed_any = metadata.removed.map(greater_or_equal_any).unwrap_or(false);
if is_removed_any {
tracing::warn!("endpoint {} is deprecated, but also removed in one of more server-passed versions: {:?}", metadata.name, versions)
} else {
tracing::warn!(
"endpoint {} is deprecated in one of more server-passed versions: {:?}",
metadata.name,
versions
)
match metadata.versioning_decision_for(versions) {
VersioningDecision::Removed => Err(IntoHttpError::EndpointRemoved(
metadata.removed.expect("VersioningDecision::Removed implies metadata.removed"),
)),
VersioningDecision::Stable { any_deprecated, all_deprecated, any_removed } => {
if any_removed {
if all_deprecated {
warn!(
"endpoint {} is removed in some (and deprecated in ALL) of the following versions: {:?}",
metadata.name,
versions
);
} else if any_deprecated {
warn!(
"endpoint {} is removed (and deprecated) in some of the following versions: {:?}",
metadata.name,
versions
);
} else {
unreachable!("any_removed implies *_deprecated");
}
} else if all_deprecated {
warn!(
"endpoint {} is deprecated in ALL of the following versions: {:?}",
metadata.name, versions
);
} else if any_deprecated {
warn!(
"endpoint {} is deprecated in some of the following versions: {:?}",
metadata.name, versions
);
}
}
if let Some(r0) = r0 {
if versions.iter().all(|&v| v == MatrixVersion::V1_0) {
// Endpoint was added in 1.0, we return the r0 variant.
return Ok(r0);
if let Some(r0) = r0 {
if versions.iter().all(|&v| v == MatrixVersion::V1_0) {
// Endpoint was added in 1.0, we return the r0 variant.
return Ok(r0);
}
}
Ok(stable.expect("metadata.added enforces the stable path to exist"))
}
return Ok(stable.expect("metadata.added enforces the stable path to exist"));
VersioningDecision::Unstable => unstable.ok_or(IntoHttpError::NoUnstablePath),
}
unstable.ok_or(IntoHttpError::NoUnstablePath)
}

View File

@ -59,6 +59,69 @@ pub struct Metadata {
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.
///
/// Matrix, since fall 2021, has a quarterly release schedule, using a global `vX.Y` versioning