From f0177dc42973466383cb673a226df464fff8aa4b Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Mon, 11 Apr 2022 15:44:03 +0200 Subject: [PATCH] api: Add `Metadata::versioning_decision_for` --- crates/ruma-common/src/api.rs | 87 ++++++++++++-------------- crates/ruma-common/src/api/metadata.rs | 63 +++++++++++++++++++ 2 files changed, 104 insertions(+), 46 deletions(-) diff --git a/crates/ruma-common/src/api.rs b/crates/ruma-common/src/api.rs index 3620bf83..e672c70a 100644 --- a/crates/ruma-common/src/api.rs +++ b/crates/ruma-common/src/api.rs @@ -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>, stable: Option>, ) -> Result, 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) } diff --git a/crates/ruma-common/src/api/metadata.rs b/crates/ruma-common/src/api/metadata.rs index ca5c3a03..4e7cc121 100644 --- a/crates/ruma-common/src/api/metadata.rs +++ b/crates/ruma-common/src/api/metadata.rs @@ -59,6 +59,69 @@ pub struct Metadata { pub removed: Option, } +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