api: Replace path fields in Metadata with new VersionHistory type
Co-authored-by: Jonathan de Jong <jonathan@automatia.nl>
This commit is contained in:
parent
451a50a77b
commit
ec31badd84
@ -197,7 +197,7 @@ pub use ruma_macros::ruma_api;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
|
|
||||||
pub use metadata::{MatrixVersion, Metadata, VersioningDecision};
|
pub use metadata::{MatrixVersion, Metadata, VersionHistory, VersioningDecision};
|
||||||
|
|
||||||
use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError};
|
use error::{FromHttpRequestError, FromHttpResponseError, IntoHttpError};
|
||||||
|
|
||||||
|
@ -292,3 +292,23 @@ impl fmt::Display for UnknownVersionError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StdError for UnknownVersionError {}
|
impl StdError for UnknownVersionError {}
|
||||||
|
|
||||||
|
/// An error that happens when an incorrect amount of arguments have been passed to PathData parts
|
||||||
|
/// formatting.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
pub struct IncorrectArgumentCount {
|
||||||
|
/// The expected amount of arguments.
|
||||||
|
pub expected: usize,
|
||||||
|
|
||||||
|
/// The amount of arguments received.
|
||||||
|
pub got: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for IncorrectArgumentCount {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "incorrect path argument count, expected {}, got {}", self.expected, self.got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StdError for IncorrectArgumentCount {}
|
||||||
|
@ -26,82 +26,17 @@ pub struct Metadata {
|
|||||||
/// A unique identifier for this endpoint.
|
/// A unique identifier for this endpoint.
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
|
||||||
/// The unstable path of this endpoint's URL, often `None`, used for developmental
|
|
||||||
/// purposes.
|
|
||||||
pub unstable_path: Option<&'static str>,
|
|
||||||
|
|
||||||
/// The pre-v1.1 version of this endpoint's URL, `None` for post-v1.1 endpoints,
|
|
||||||
/// supplemental to `stable_path`.
|
|
||||||
pub r0_path: Option<&'static str>,
|
|
||||||
|
|
||||||
/// The path of this endpoint's URL, with variable names where path parameters should be
|
|
||||||
/// filled in during a request.
|
|
||||||
pub stable_path: Option<&'static str>,
|
|
||||||
|
|
||||||
/// Whether or not this endpoint is rate limited by the server.
|
/// Whether or not this endpoint is rate limited by the server.
|
||||||
pub rate_limited: bool,
|
pub rate_limited: bool,
|
||||||
|
|
||||||
/// What authentication scheme the server uses for this endpoint.
|
/// What authentication scheme the server uses for this endpoint.
|
||||||
pub authentication: AuthScheme,
|
pub authentication: AuthScheme,
|
||||||
|
|
||||||
/// The matrix version that this endpoint was added in.
|
/// All info pertaining to an endpoint's (historic) paths, deprecation version, and removal.
|
||||||
///
|
pub history: VersionHistory,
|
||||||
/// Is None when this endpoint is unstable/unreleased.
|
|
||||||
pub added: Option<MatrixVersion>,
|
|
||||||
|
|
||||||
/// The matrix version that deprecated this endpoint.
|
|
||||||
///
|
|
||||||
/// Deprecation often precedes one matrix version before removal.
|
|
||||||
///
|
|
||||||
/// This will make [`try_into_http_request`](super::OutgoingRequest::try_into_http_request)
|
|
||||||
/// emit a warning, see the corresponding documentation for more information.
|
|
||||||
pub deprecated: Option<MatrixVersion>,
|
|
||||||
|
|
||||||
/// The matrix version that removed this endpoint.
|
|
||||||
///
|
|
||||||
/// This will make [`try_into_http_request`](super::OutgoingRequest::try_into_http_request)
|
|
||||||
/// emit an error, see the corresponding documentation for more information.
|
|
||||||
pub removed: Option<MatrixVersion>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Metadata {
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate the endpoint URL for this endpoint.
|
/// Generate the endpoint URL for this endpoint.
|
||||||
pub fn make_endpoint_url(
|
pub fn make_endpoint_url(
|
||||||
&self,
|
&self,
|
||||||
@ -110,7 +45,7 @@ impl Metadata {
|
|||||||
path_args: &[&dyn Display],
|
path_args: &[&dyn Display],
|
||||||
query_string: Option<&str>,
|
query_string: Option<&str>,
|
||||||
) -> Result<String, IntoHttpError> {
|
) -> Result<String, IntoHttpError> {
|
||||||
let path_with_placeholders = self.select_path(versions)?;
|
let path_with_placeholders = self.history.select_path(versions, self.name)?;
|
||||||
|
|
||||||
let mut res = base_url.strip_suffix('/').unwrap_or(base_url).to_owned();
|
let mut res = base_url.strip_suffix('/').unwrap_or(base_url).to_owned();
|
||||||
let mut segments = path_with_placeholders.split('/');
|
let mut segments = path_with_placeholders.split('/');
|
||||||
@ -142,9 +77,47 @@ impl Metadata {
|
|||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This function helps picks the right path (or an error) from a set of matrix versions.
|
/// The complete history of this endpoint as far as Ruma knows, together with all variants on
|
||||||
fn select_path(&self, versions: &[MatrixVersion]) -> Result<&str, IntoHttpError> {
|
/// versions stable and unstable.
|
||||||
|
///
|
||||||
|
/// The amount and positioning of path variables are the same over all path variants.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[allow(clippy::exhaustive_structs)]
|
||||||
|
pub struct VersionHistory {
|
||||||
|
/// A list of unstable paths over this endpoint's history.
|
||||||
|
///
|
||||||
|
/// For endpoint querying purposes, the last item will be used.
|
||||||
|
pub unstable_paths: &'static [&'static str],
|
||||||
|
|
||||||
|
/// A list of path versions, mapped to Matrix versions.
|
||||||
|
///
|
||||||
|
/// Sorted (ascending) by Matrix version, will not mix major versions.
|
||||||
|
pub stable_paths: &'static [(MatrixVersion, &'static str)],
|
||||||
|
|
||||||
|
/// The Matrix version that deprecated this endpoint.
|
||||||
|
///
|
||||||
|
/// Deprecation often precedes one Matrix version before removal.
|
||||||
|
///
|
||||||
|
/// This will make [`try_into_http_request`](super::OutgoingRequest::try_into_http_request)
|
||||||
|
/// emit a warning, see the corresponding documentation for more information.
|
||||||
|
pub deprecated: Option<MatrixVersion>,
|
||||||
|
|
||||||
|
/// The Matrix version that removed this endpoint.
|
||||||
|
///
|
||||||
|
/// This will make [`try_into_http_request`](super::OutgoingRequest::try_into_http_request)
|
||||||
|
/// emit an error, see the corresponding documentation for more information.
|
||||||
|
pub removed: Option<MatrixVersion>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionHistory {
|
||||||
|
// This function helps picks the right path (or an error) from a set of Matrix versions.
|
||||||
|
fn select_path(
|
||||||
|
&self,
|
||||||
|
versions: &[MatrixVersion],
|
||||||
|
name: &str,
|
||||||
|
) -> Result<&'static str, IntoHttpError> {
|
||||||
match self.versioning_decision_for(versions) {
|
match self.versioning_decision_for(versions) {
|
||||||
VersioningDecision::Removed => Err(IntoHttpError::EndpointRemoved(
|
VersioningDecision::Removed => Err(IntoHttpError::EndpointRemoved(
|
||||||
self.removed.expect("VersioningDecision::Removed implies metadata.removed"),
|
self.removed.expect("VersioningDecision::Removed implies metadata.removed"),
|
||||||
@ -153,55 +126,134 @@ impl Metadata {
|
|||||||
if any_removed {
|
if any_removed {
|
||||||
if all_deprecated {
|
if all_deprecated {
|
||||||
warn!(
|
warn!(
|
||||||
"endpoint {} is removed in some (and deprecated in ALL) \
|
"endpoint {name} is removed in some (and deprecated in ALL) \
|
||||||
of the following versions: {:?}",
|
of the following versions: {versions:?}",
|
||||||
self.name, versions
|
|
||||||
);
|
);
|
||||||
} else if any_deprecated {
|
} else if any_deprecated {
|
||||||
warn!(
|
warn!(
|
||||||
"endpoint {} is removed (and deprecated) in some of the \
|
"endpoint {name} is removed (and deprecated) in some of the \
|
||||||
following versions: {:?}",
|
following versions: {versions:?}",
|
||||||
self.name, versions
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
unreachable!("any_removed implies *_deprecated");
|
unreachable!("any_removed implies *_deprecated");
|
||||||
}
|
}
|
||||||
} else if all_deprecated {
|
} else if all_deprecated {
|
||||||
warn!(
|
warn!(
|
||||||
"endpoint {} is deprecated in ALL of the following versions: {:?}",
|
"endpoint {name} is deprecated in ALL of the following versions: \
|
||||||
self.name, versions
|
{versions:?}",
|
||||||
);
|
);
|
||||||
} else if any_deprecated {
|
} else if any_deprecated {
|
||||||
warn!(
|
warn!(
|
||||||
"endpoint {} is deprecated in some of the following versions: {:?}",
|
"endpoint {name} is deprecated in some of the following versions: \
|
||||||
self.name, versions
|
{versions:?}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(r0) = self.r0_path {
|
Ok(self
|
||||||
if versions.iter().all(|&v| v == MatrixVersion::V1_0) {
|
.stable_endpoint_for(versions)
|
||||||
// Endpoint was added in 1.0, we return the r0 variant.
|
.expect("VersioningDecision::Stable implies that a stable path exists"))
|
||||||
return Ok(r0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.stable_path.expect("metadata.added enforces the stable path to exist"))
|
|
||||||
}
|
}
|
||||||
VersioningDecision::Unstable => self.unstable_path.ok_or(IntoHttpError::NoUnstablePath),
|
VersioningDecision::Unstable => self.unstable().ok_or(IntoHttpError::NoUnstablePath),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Will decide how a particular set of Matrix versions sees an endpoint.
|
||||||
|
///
|
||||||
|
/// It will only return `Deprecated` or `Removed` 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_or(false, greater_or_equal_all) {
|
||||||
|
return VersioningDecision::Removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if *any* version marks this endpoint as stable.
|
||||||
|
if self.added_version().map_or(false, greater_or_equal_any) {
|
||||||
|
let all_deprecated = self.deprecated.map_or(false, greater_or_equal_all);
|
||||||
|
|
||||||
|
return VersioningDecision::Stable {
|
||||||
|
any_deprecated: all_deprecated
|
||||||
|
|| self.deprecated.map_or(false, greater_or_equal_any),
|
||||||
|
all_deprecated,
|
||||||
|
any_removed: self.removed.map_or(false, greater_or_equal_any),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
VersioningDecision::Unstable
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Will return the *first* version this path was added in.
|
||||||
|
///
|
||||||
|
/// Is None when this endpoint is unstable/unreleased.
|
||||||
|
pub fn added_version(&self) -> Option<MatrixVersion> {
|
||||||
|
self.stable_paths.first().map(|(x, _)| *x)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Picks the last unstable path, if it exists.
|
||||||
|
pub fn unstable(&self) -> Option<&'static str> {
|
||||||
|
self.unstable_paths.last().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all path variants in canon form, for use in server routers.
|
||||||
|
pub fn all_paths(&self) -> Vec<&'static str> {
|
||||||
|
let unstable = self.unstable_paths.iter().copied();
|
||||||
|
let stable = self.stable_paths.iter().map(|(_, y)| *y);
|
||||||
|
unstable.chain(stable).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all unstable path variants in canon form.
|
||||||
|
pub fn all_unstable_paths(&self) -> Vec<&'static str> {
|
||||||
|
self.unstable_paths.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all stable path variants in canon form, with corresponding Matrix version.
|
||||||
|
pub fn all_versioned_stable_paths(&self) -> Vec<(MatrixVersion, &'static str)> {
|
||||||
|
self.stable_paths.iter().map(|(version, data)| (*version, *data)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The path that should be used to query the endpoint, given a series of versions.
|
||||||
|
///
|
||||||
|
/// This will pick the latest path that the version accepts.
|
||||||
|
///
|
||||||
|
/// This will return an endpoint in the following format;
|
||||||
|
/// - `/_matrix/client/versions`
|
||||||
|
/// - `/_matrix/client/hello/:world` (`:world` is a path replacement parameter)
|
||||||
|
///
|
||||||
|
/// Note: This will not keep in mind endpoint removals, check with
|
||||||
|
/// [`versioning_decision_for`](VersionHistory::versioning_decision_for) to see if this endpoint
|
||||||
|
/// is still available.
|
||||||
|
pub fn stable_endpoint_for(&self, versions: &[MatrixVersion]) -> Option<&'static str> {
|
||||||
|
// Go reverse, to check the "latest" version first.
|
||||||
|
for (ver, path) in self.stable_paths.iter().rev() {
|
||||||
|
// Check if any of the versions are equal or greater than the version the path needs.
|
||||||
|
if versions.iter().any(|v| v.is_superset_of(*ver)) {
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A versioning "decision" derived from a set of matrix versions.
|
/// A versioning "decision" derived from a set of Matrix versions.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
#[allow(clippy::exhaustive_enums)]
|
#[allow(clippy::exhaustive_enums)]
|
||||||
pub enum VersioningDecision {
|
pub enum VersioningDecision {
|
||||||
/// The unstable endpoint should be used.
|
/// The unstable endpoint should be used.
|
||||||
Unstable,
|
Unstable,
|
||||||
|
|
||||||
/// The stable endpoint should be used.
|
/// 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 {
|
Stable {
|
||||||
/// If any version denoted deprecation.
|
/// If any version denoted deprecation.
|
||||||
any_deprecated: bool,
|
any_deprecated: bool,
|
||||||
@ -212,6 +264,7 @@ pub enum VersioningDecision {
|
|||||||
/// If any version denoted removal.
|
/// If any version denoted removal.
|
||||||
any_removed: bool,
|
any_removed: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// This endpoint was removed in all versions, it should not be used.
|
/// This endpoint was removed in all versions, it should not be used.
|
||||||
Removed,
|
Removed,
|
||||||
}
|
}
|
||||||
@ -363,44 +416,46 @@ mod tests {
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
AuthScheme,
|
AuthScheme,
|
||||||
MatrixVersion::{V1_0, V1_1, V1_2},
|
MatrixVersion::{self, V1_0, V1_1, V1_2, V1_3},
|
||||||
Metadata,
|
Metadata, VersionHistory,
|
||||||
};
|
};
|
||||||
use crate::api::error::IntoHttpError;
|
use crate::api::error::IntoHttpError;
|
||||||
|
|
||||||
const BASE: Metadata = Metadata {
|
fn stable_only_metadata(stable_paths: &'static [(MatrixVersion, &'static str)]) -> Metadata {
|
||||||
description: "",
|
Metadata {
|
||||||
method: Method::GET,
|
description: "",
|
||||||
name: "test_endpoint",
|
method: Method::GET,
|
||||||
unstable_path: None,
|
name: "test_endpoint",
|
||||||
r0_path: None,
|
rate_limited: false,
|
||||||
stable_path: None,
|
authentication: AuthScheme::None,
|
||||||
rate_limited: false,
|
history: VersionHistory {
|
||||||
authentication: AuthScheme::None,
|
unstable_paths: &[],
|
||||||
added: None,
|
stable_paths,
|
||||||
deprecated: None,
|
deprecated: None,
|
||||||
removed: None,
|
removed: None,
|
||||||
};
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO add test that can hook into tracing and verify the deprecation warning is emitted
|
// TODO add test that can hook into tracing and verify the deprecation warning is emitted
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_simple_endpoint_url() {
|
fn make_simple_endpoint_url() {
|
||||||
let meta = Metadata { added: Some(V1_0), stable_path: Some("/s"), ..BASE };
|
let meta = stable_only_metadata(&[(V1_0, "/s")]);
|
||||||
let url = meta.make_endpoint_url(&[V1_0], "https://example.org", &[], None).unwrap();
|
let url = meta.make_endpoint_url(&[V1_0], "https://example.org", &[], None).unwrap();
|
||||||
assert_eq!(url, "https://example.org/s");
|
assert_eq!(url, "https://example.org/s");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_endpoint_url_with_path_args() {
|
fn make_endpoint_url_with_path_args() {
|
||||||
let meta = Metadata { added: Some(V1_0), stable_path: Some("/s/:x"), ..BASE };
|
let meta = stable_only_metadata(&[(V1_0, "/s/:x")]);
|
||||||
let url = meta.make_endpoint_url(&[V1_0], "https://example.org", &[&"123"], None).unwrap();
|
let url = meta.make_endpoint_url(&[V1_0], "https://example.org", &[&"123"], None).unwrap();
|
||||||
assert_eq!(url, "https://example.org/s/123");
|
assert_eq!(url, "https://example.org/s/123");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn make_endpoint_url_with_query() {
|
fn make_endpoint_url_with_query() {
|
||||||
let meta = Metadata { added: Some(V1_0), stable_path: Some("/s/"), ..BASE };
|
let meta = stable_only_metadata(&[(V1_0, "/s/")]);
|
||||||
let url =
|
let url =
|
||||||
meta.make_endpoint_url(&[V1_0], "https://example.org", &[], Some("foo=bar")).unwrap();
|
meta.make_endpoint_url(&[V1_0], "https://example.org", &[], Some("foo=bar")).unwrap();
|
||||||
assert_eq!(url, "https://example.org/s/?foo=bar");
|
assert_eq!(url, "https://example.org/s/?foo=bar");
|
||||||
@ -409,59 +464,62 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn make_endpoint_url_wrong_num_path_args() {
|
fn make_endpoint_url_wrong_num_path_args() {
|
||||||
let meta = Metadata { added: Some(V1_0), stable_path: Some("/s/:x"), ..BASE };
|
let meta = stable_only_metadata(&[(V1_0, "/s/:x")]);
|
||||||
_ = meta.make_endpoint_url(&[V1_0], "https://example.org", &[], None);
|
_ = meta.make_endpoint_url(&[V1_0], "https://example.org", &[], None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EMPTY: VersionHistory =
|
||||||
|
VersionHistory { unstable_paths: &[], stable_paths: &[], deprecated: None, removed: None };
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_stable() {
|
fn select_latest_stable() {
|
||||||
let meta = Metadata { added: Some(V1_1), stable_path: Some("s"), ..BASE };
|
let hist = VersionHistory { stable_paths: &[(V1_1, "/s")], ..EMPTY };
|
||||||
assert_matches!(meta.select_path(&[V1_0, V1_1]), Ok("s"));
|
assert_matches!(hist.select_path(&[V1_0, V1_1], "test_endpoint"), Ok("/s"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_unstable() {
|
fn select_unstable() {
|
||||||
let meta = Metadata { unstable_path: Some("u"), ..BASE };
|
let hist = VersionHistory { unstable_paths: &["/u"], ..EMPTY };
|
||||||
assert_matches!(meta.select_path(&[V1_0]), Ok("u"));
|
assert_matches!(hist.select_path(&[V1_0], "test_endpoint"), Ok("/u"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_r0() {
|
fn select_r0() {
|
||||||
let meta = Metadata { added: Some(V1_0), r0_path: Some("r"), ..BASE };
|
let hist = VersionHistory { stable_paths: &[(V1_0, "/r")], ..EMPTY };
|
||||||
assert_matches!(meta.select_path(&[V1_0]), Ok("r"));
|
assert_matches!(hist.select_path(&[V1_0], "test_endpoint"), Ok("/r"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_removed_err() {
|
fn select_removed_err() {
|
||||||
let meta = Metadata {
|
let hist = VersionHistory {
|
||||||
added: Some(V1_0),
|
stable_paths: &[(V1_0, "/r"), (V1_1, "/s")],
|
||||||
deprecated: Some(V1_1),
|
unstable_paths: &["/u"],
|
||||||
removed: Some(V1_2),
|
deprecated: Some(V1_2),
|
||||||
unstable_path: Some("u"),
|
removed: Some(V1_3),
|
||||||
r0_path: Some("r"),
|
|
||||||
stable_path: Some("s"),
|
|
||||||
..BASE
|
|
||||||
};
|
};
|
||||||
assert_matches!(meta.select_path(&[V1_2]), Err(IntoHttpError::EndpointRemoved(V1_2)));
|
assert_matches!(
|
||||||
|
hist.select_path(&[V1_3], "test_endpoint"),
|
||||||
|
Err(IntoHttpError::EndpointRemoved(V1_3))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn partially_removed_but_stable() {
|
fn partially_removed_but_stable() {
|
||||||
let meta = Metadata {
|
let hist = VersionHistory {
|
||||||
added: Some(V1_0),
|
stable_paths: &[(V1_0, "/r"), (V1_1, "/s")],
|
||||||
deprecated: Some(V1_1),
|
unstable_paths: &[],
|
||||||
removed: Some(V1_2),
|
deprecated: Some(V1_2),
|
||||||
r0_path: Some("r"),
|
removed: Some(V1_3),
|
||||||
stable_path: Some("s"),
|
|
||||||
..BASE
|
|
||||||
};
|
};
|
||||||
assert_matches!(meta.select_path(&[V1_1]), Ok("s"));
|
assert_matches!(hist.select_path(&[V1_2], "test_endpoint"), Ok("/s"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_unstable() {
|
fn no_unstable() {
|
||||||
let meta =
|
let hist = VersionHistory { stable_paths: &[(V1_1, "/s")], ..EMPTY };
|
||||||
Metadata { added: Some(V1_1), r0_path: Some("r"), stable_path: Some("s"), ..BASE };
|
assert_matches!(
|
||||||
assert_matches!(meta.select_path(&[V1_0]), Err(IntoHttpError::NoUnstablePath));
|
hist.select_path(&[V1_0], "test_endpoint"),
|
||||||
|
Err(IntoHttpError::NoUnstablePath)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ use ruma_common::{
|
|||||||
FromHttpRequestError, FromHttpResponseError, IntoHttpError, MatrixError, ServerError,
|
FromHttpRequestError, FromHttpResponseError, IntoHttpError, MatrixError, ServerError,
|
||||||
},
|
},
|
||||||
AuthScheme, EndpointError, IncomingRequest, IncomingResponse, MatrixVersion, Metadata,
|
AuthScheme, EndpointError, IncomingRequest, IncomingResponse, MatrixVersion, Metadata,
|
||||||
OutgoingRequest, OutgoingResponse, SendAccessToken,
|
OutgoingRequest, OutgoingResponse, SendAccessToken, VersionHistory,
|
||||||
},
|
},
|
||||||
OwnedRoomAliasId, OwnedRoomId,
|
OwnedRoomAliasId, OwnedRoomId,
|
||||||
};
|
};
|
||||||
@ -27,14 +27,18 @@ const METADATA: Metadata = Metadata {
|
|||||||
description: "Add an alias to a room.",
|
description: "Add an alias to a room.",
|
||||||
method: Method::PUT,
|
method: Method::PUT,
|
||||||
name: "create_alias",
|
name: "create_alias",
|
||||||
unstable_path: Some("/_matrix/client/unstable/directory/room/:room_alias"),
|
|
||||||
r0_path: Some("/_matrix/client/r0/directory/room/:room_alias"),
|
|
||||||
stable_path: Some("/_matrix/client/v3/directory/room/:room_alias"),
|
|
||||||
rate_limited: false,
|
rate_limited: false,
|
||||||
authentication: AuthScheme::None,
|
authentication: AuthScheme::None,
|
||||||
added: Some(MatrixVersion::V1_0),
|
|
||||||
deprecated: Some(MatrixVersion::V1_1),
|
history: VersionHistory {
|
||||||
removed: Some(MatrixVersion::V1_2),
|
unstable_paths: &["/_matrix/client/unstable/directory/room/:room_alias"],
|
||||||
|
stable_paths: &[
|
||||||
|
(MatrixVersion::V1_0, "/_matrix/client/r0/directory/room/:room_alias"),
|
||||||
|
(MatrixVersion::V1_1, "/_matrix/client/v3/directory/room/:room_alias"),
|
||||||
|
],
|
||||||
|
deprecated: Some(MatrixVersion::V1_2),
|
||||||
|
removed: Some(MatrixVersion::V1_3),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl OutgoingRequest for Request {
|
impl OutgoingRequest for Request {
|
||||||
|
@ -12,7 +12,7 @@ ruma_api! {
|
|||||||
name: "some_endpoint",
|
name: "some_endpoint",
|
||||||
unstable_path: "/_matrix/some/msc1234/endpoint/:baz",
|
unstable_path: "/_matrix/some/msc1234/endpoint/:baz",
|
||||||
r0_path: "/_matrix/some/r0/endpoint/:baz",
|
r0_path: "/_matrix/some/r0/endpoint/:baz",
|
||||||
stable_path: "/_matrix/some/v1/endpoint/:baz",
|
stable_path: "/_matrix/some/v3/endpoint/:baz",
|
||||||
rate_limited: false,
|
rate_limited: false,
|
||||||
authentication: None,
|
authentication: None,
|
||||||
added: 1.0,
|
added: 1.0,
|
||||||
@ -61,11 +61,16 @@ ruma_api! {
|
|||||||
fn main() {
|
fn main() {
|
||||||
use ruma_common::api::MatrixVersion;
|
use ruma_common::api::MatrixVersion;
|
||||||
|
|
||||||
assert_eq!(METADATA.unstable_path, Some("/_matrix/some/msc1234/endpoint/:baz"));
|
assert_eq!(METADATA.history.all_unstable_paths(), &["/_matrix/some/msc1234/endpoint/:baz"],);
|
||||||
assert_eq!(METADATA.r0_path, Some("/_matrix/some/r0/endpoint/:baz"));
|
assert_eq!(
|
||||||
assert_eq!(METADATA.stable_path, Some("/_matrix/some/v1/endpoint/:baz"));
|
METADATA.history.all_versioned_stable_paths(),
|
||||||
|
&[
|
||||||
|
(MatrixVersion::V1_0, "/_matrix/some/r0/endpoint/:baz"),
|
||||||
|
(MatrixVersion::V1_1, "/_matrix/some/v3/endpoint/:baz")
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(METADATA.added, Some(MatrixVersion::V1_0));
|
assert_eq!(METADATA.history.added_version(), Some(MatrixVersion::V1_0));
|
||||||
assert_eq!(METADATA.deprecated, Some(MatrixVersion::V1_1));
|
assert_eq!(METADATA.history.deprecated, Some(MatrixVersion::V1_1));
|
||||||
assert_eq!(METADATA.removed, Some(MatrixVersion::V1_2));
|
assert_eq!(METADATA.history.removed, Some(MatrixVersion::V1_2));
|
||||||
}
|
}
|
||||||
|
@ -65,14 +65,9 @@ impl Api {
|
|||||||
let description = &metadata.description;
|
let description = &metadata.description;
|
||||||
let method = &metadata.method;
|
let method = &metadata.method;
|
||||||
let name = &metadata.name;
|
let name = &metadata.name;
|
||||||
let unstable_path = util::map_option_literal(&metadata.unstable_path);
|
|
||||||
let r0_path = util::map_option_literal(&metadata.r0_path);
|
|
||||||
let stable_path = util::map_option_literal(&metadata.stable_path);
|
|
||||||
let rate_limited = &self.metadata.rate_limited;
|
let rate_limited = &self.metadata.rate_limited;
|
||||||
let authentication = &self.metadata.authentication;
|
let authentication = &self.metadata.authentication;
|
||||||
let added = util::map_option_literal(&metadata.added);
|
let history = &self.metadata.history;
|
||||||
let deprecated = util::map_option_literal(&metadata.deprecated);
|
|
||||||
let removed = util::map_option_literal(&metadata.removed);
|
|
||||||
|
|
||||||
let error_ty = self.error_ty.map_or_else(
|
let error_ty = self.error_ty.map_or_else(
|
||||||
|| quote! { #ruma_common::api::error::MatrixError },
|
|| quote! { #ruma_common::api::error::MatrixError },
|
||||||
@ -93,14 +88,9 @@ impl Api {
|
|||||||
description: #description,
|
description: #description,
|
||||||
method: #http::Method::#method,
|
method: #http::Method::#method,
|
||||||
name: #name,
|
name: #name,
|
||||||
unstable_path: #unstable_path,
|
|
||||||
r0_path: #r0_path,
|
|
||||||
stable_path: #stable_path,
|
|
||||||
added: #added,
|
|
||||||
deprecated: #deprecated,
|
|
||||||
removed: #removed,
|
|
||||||
rate_limited: #rate_limited,
|
rate_limited: #rate_limited,
|
||||||
authentication: #ruma_common::api::AuthScheme::#authentication,
|
authentication: #ruma_common::api::AuthScheme::#authentication,
|
||||||
|
history: #history,
|
||||||
};
|
};
|
||||||
|
|
||||||
#request
|
#request
|
||||||
@ -112,20 +102,15 @@ impl Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_paths(&self) -> syn::Result<()> {
|
fn check_paths(&self) -> syn::Result<()> {
|
||||||
let mut path_iter = self
|
let mut path_iter = self.metadata.history.entries.iter().filter_map(|entry| entry.path());
|
||||||
.metadata
|
|
||||||
.unstable_path
|
|
||||||
.iter()
|
|
||||||
.chain(&self.metadata.r0_path)
|
|
||||||
.chain(&self.metadata.stable_path);
|
|
||||||
|
|
||||||
let path = path_iter.next().ok_or_else(|| {
|
let path = path_iter.next().ok_or_else(|| {
|
||||||
syn::Error::new(Span::call_site(), "at least one path metadata field must be set")
|
syn::Error::new(Span::call_site(), "at least one path metadata field must be set")
|
||||||
})?;
|
})?;
|
||||||
let path_args = get_path_args(&path.value());
|
let path_args = path.args();
|
||||||
|
|
||||||
for extra_path in path_iter {
|
for extra_path in path_iter {
|
||||||
let extra_path_args = get_path_args(&extra_path.value());
|
let extra_path_args = extra_path.args();
|
||||||
if extra_path_args != path_args {
|
if extra_path_args != path_args {
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
@ -270,7 +255,3 @@ fn ensure_feature_presence() -> Option<&'static syn::Error> {
|
|||||||
|
|
||||||
RESULT.as_ref().err()
|
RESULT.as_ref().err()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_path_args(path: &str) -> Vec<String> {
|
|
||||||
path.split('/').filter_map(|s| s.strip_prefix(':').map(ToOwned::to_owned)).collect()
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Details of the `metadata` section of the procedural macro.
|
//! Details of the `metadata` section of the procedural macro.
|
||||||
|
|
||||||
use quote::ToTokens;
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
use syn::{
|
use syn::{
|
||||||
braced,
|
braced,
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
@ -35,29 +36,14 @@ pub struct Metadata {
|
|||||||
/// The name field.
|
/// The name field.
|
||||||
pub name: LitStr,
|
pub name: LitStr,
|
||||||
|
|
||||||
/// The unstable path field.
|
|
||||||
pub unstable_path: Option<EndpointPath>,
|
|
||||||
|
|
||||||
/// The pre-v1.1 path field.
|
|
||||||
pub r0_path: Option<EndpointPath>,
|
|
||||||
|
|
||||||
/// The stable path field.
|
|
||||||
pub stable_path: Option<EndpointPath>,
|
|
||||||
|
|
||||||
/// The rate_limited field.
|
/// The rate_limited field.
|
||||||
pub rate_limited: LitBool,
|
pub rate_limited: LitBool,
|
||||||
|
|
||||||
/// The authentication field.
|
/// The authentication field.
|
||||||
pub authentication: AuthScheme,
|
pub authentication: AuthScheme,
|
||||||
|
|
||||||
/// The added field.
|
/// The version history field.
|
||||||
pub added: Option<MatrixVersionLiteral>,
|
pub history: History,
|
||||||
|
|
||||||
/// The deprecated field.
|
|
||||||
pub deprecated: Option<MatrixVersionLiteral>,
|
|
||||||
|
|
||||||
/// The removed field.
|
|
||||||
pub removed: Option<MatrixVersionLiteral>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_field<T: ToTokens>(field: &mut Option<T>, value: T) -> syn::Result<()> {
|
fn set_field<T: ToTokens>(field: &mut Option<T>, value: T) -> syn::Result<()> {
|
||||||
@ -116,97 +102,98 @@ impl Parse for Metadata {
|
|||||||
let missing_field =
|
let missing_field =
|
||||||
|name| syn::Error::new_spanned(metadata_kw, format!("missing field `{}`", name));
|
|name| syn::Error::new_spanned(metadata_kw, format!("missing field `{}`", name));
|
||||||
|
|
||||||
let stable_or_r0 = stable_path.as_ref().or(r0_path.as_ref());
|
// Construct the History object.
|
||||||
|
let history = {
|
||||||
|
let stable_or_r0 = stable_path.as_ref().or(r0_path.as_ref());
|
||||||
|
|
||||||
if let Some(path) = stable_or_r0 {
|
if let Some(path) = stable_or_r0 {
|
||||||
if added.is_none() {
|
if added.is_none() {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
path,
|
path,
|
||||||
"stable path was defined, while `added` version was not defined",
|
"stable path was defined, while `added` version was not defined",
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(deprecated) = &deprecated {
|
if let Some(deprecated) = &deprecated {
|
||||||
if added.is_none() {
|
if added.is_none() {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
deprecated,
|
deprecated,
|
||||||
"deprecated version is defined while added version is not defined",
|
"deprecated version is defined while added version is not defined",
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// note: It is possible that matrix will remove endpoints in a single version, while not
|
// Note: It is possible that Matrix will remove endpoints in a single version, while
|
||||||
// having a deprecation version inbetween, but that would not be allowed by their own
|
// not having a deprecation version inbetween, but that would not be allowed by their
|
||||||
// deprecation policy, so lets just assume there's always a deprecation version before a
|
// own deprecation policy, so lets just assume there's always a deprecation version
|
||||||
// removal one.
|
// before a removal one.
|
||||||
//
|
//
|
||||||
// If matrix does so anyways, we can just alter this.
|
// If Matrix does so anyways, we can just alter this.
|
||||||
if let Some(removed) = &removed {
|
if let Some(removed) = &removed {
|
||||||
if deprecated.is_none() {
|
if deprecated.is_none() {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
removed,
|
removed,
|
||||||
"removed version is defined while deprecated version is not defined",
|
"removed version is defined while deprecated version is not defined",
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(added) = &added {
|
if let Some(added) = &added {
|
||||||
if stable_or_r0.is_none() {
|
if stable_or_r0.is_none() {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
added,
|
added,
|
||||||
"added version is defined, but no stable or r0 path exists",
|
"added version is defined, but no stable or r0 path exists",
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(r0) = &r0_path {
|
if let Some(r0) = &r0_path {
|
||||||
let added = added.as_ref().expect("we error if r0 or stable is defined without added");
|
let added =
|
||||||
|
added.as_ref().expect("we error if r0 or stable is defined without added");
|
||||||
|
|
||||||
if added.major.get() == 1 && added.minor > 0 {
|
if added.major.get() == 1 && added.minor > 0 {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
r0,
|
||||||
|
"r0 defined while added version is newer than v1.0",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if stable_path.is_none() {
|
||||||
|
return Err(syn::Error::new_spanned(r0, "r0 defined without stable path"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r0.value().contains("/r0/") {
|
||||||
|
return Err(syn::Error::new_spanned(r0, "r0 endpoint does not contain /r0/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(stable) = &stable_path {
|
||||||
|
if stable.value().contains("/r0/") {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
stable,
|
||||||
|
"stable endpoint contains /r0/ (did you make a copy-paste error?)",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if unstable_path.is_none() && r0_path.is_none() && stable_path.is_none() {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
r0,
|
metadata_kw,
|
||||||
"r0 defined while added version is newer than v1.0",
|
"need to define one of [r0_path, stable_path, unstable_path]",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if stable_path.is_none() {
|
History::construct(deprecated, removed, unstable_path, r0_path, stable_path.zip(added))
|
||||||
return Err(syn::Error::new_spanned(r0, "r0 defined without stable path"));
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if !r0.value().contains("/r0/") {
|
|
||||||
return Err(syn::Error::new_spanned(r0, "r0 endpoint does not contain /r0/"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(stable) = &stable_path {
|
|
||||||
if stable.value().contains("/r0/") {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
stable,
|
|
||||||
"stable endpoint contains /r0/ (did you make a copy-paste error?)",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if unstable_path.is_none() && r0_path.is_none() && stable_path.is_none() {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
metadata_kw,
|
|
||||||
"need to define one of [r0_path, stable_path, unstable_path]",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
description: description.ok_or_else(|| missing_field("description"))?,
|
description: description.ok_or_else(|| missing_field("description"))?,
|
||||||
method: method.ok_or_else(|| missing_field("method"))?,
|
method: method.ok_or_else(|| missing_field("method"))?,
|
||||||
name: name.ok_or_else(|| missing_field("name"))?,
|
name: name.ok_or_else(|| missing_field("name"))?,
|
||||||
unstable_path,
|
|
||||||
r0_path,
|
|
||||||
stable_path,
|
|
||||||
rate_limited: rate_limited.ok_or_else(|| missing_field("rate_limited"))?,
|
rate_limited: rate_limited.ok_or_else(|| missing_field("rate_limited"))?,
|
||||||
authentication: authentication.ok_or_else(|| missing_field("authentication"))?,
|
authentication: authentication.ok_or_else(|| missing_field("authentication"))?,
|
||||||
added,
|
history,
|
||||||
deprecated,
|
|
||||||
removed,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,13 +290,127 @@ impl Parse for FieldValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct History {
|
||||||
|
pub(super) entries: Vec<HistoryEntry>,
|
||||||
|
misc: MiscVersioning,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl History {
|
||||||
|
// TODO(j0j0): remove after codebase conversion is complete
|
||||||
|
/// Construct a History object from legacy parts.
|
||||||
|
pub fn construct(
|
||||||
|
deprecated: Option<MatrixVersionLiteral>,
|
||||||
|
removed: Option<MatrixVersionLiteral>,
|
||||||
|
unstable_path: Option<EndpointPath>,
|
||||||
|
r0_path: Option<EndpointPath>,
|
||||||
|
stable_path_and_version: Option<(EndpointPath, MatrixVersionLiteral)>,
|
||||||
|
) -> Self {
|
||||||
|
// Unfortunately can't `use` associated constants
|
||||||
|
const V1_0: MatrixVersionLiteral = MatrixVersionLiteral::V1_0;
|
||||||
|
|
||||||
|
let unstable = unstable_path.map(|path| HistoryEntry::Unstable { path });
|
||||||
|
let r0 = r0_path.map(|path| HistoryEntry::Stable { path, version: V1_0 });
|
||||||
|
let stable = stable_path_and_version.map(|(path, mut version)| {
|
||||||
|
// If added in 1.0 as r0, the new stable path must be from 1.1
|
||||||
|
if r0.is_some() && version == V1_0 {
|
||||||
|
version = MatrixVersionLiteral::V1_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryEntry::Stable { path, version }
|
||||||
|
});
|
||||||
|
|
||||||
|
let misc = match (deprecated, removed) {
|
||||||
|
(None, None) => MiscVersioning::None,
|
||||||
|
(Some(deprecated), None) => MiscVersioning::Deprecated(deprecated),
|
||||||
|
(Some(deprecated), Some(removed)) => MiscVersioning::Removed { deprecated, removed },
|
||||||
|
|
||||||
|
(None, Some(_)) => unreachable!("removed implies deprecated"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let entries = [unstable, r0, stable].into_iter().flatten().collect();
|
||||||
|
|
||||||
|
History { entries, misc }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum MiscVersioning {
|
||||||
|
None,
|
||||||
|
Deprecated(MatrixVersionLiteral),
|
||||||
|
Removed { deprecated: MatrixVersionLiteral, removed: MatrixVersionLiteral },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for History {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
fn endpointpath_to_pathdata_ts(endpoint: &EndpointPath) -> String {
|
||||||
|
endpoint.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
let unstable = self.entries.iter().filter_map(|e| match e {
|
||||||
|
HistoryEntry::Unstable { path } => Some(endpointpath_to_pathdata_ts(path)),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
let versioned = self.entries.iter().filter_map(|e| match e {
|
||||||
|
HistoryEntry::Stable { path, version } => {
|
||||||
|
let path = endpointpath_to_pathdata_ts(path);
|
||||||
|
Some(quote! {( #version, #path )})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let (deprecated, removed) = match &self.misc {
|
||||||
|
MiscVersioning::None => (None, None),
|
||||||
|
MiscVersioning::Deprecated(deprecated) => (Some(deprecated), None),
|
||||||
|
MiscVersioning::Removed { deprecated, removed } => (Some(deprecated), Some(removed)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let deprecated = util::map_option_literal(&deprecated);
|
||||||
|
let removed = util::map_option_literal(&removed);
|
||||||
|
|
||||||
|
tokens.extend(quote! {
|
||||||
|
::ruma_common::api::VersionHistory {
|
||||||
|
unstable_paths: &[ #(#unstable),* ],
|
||||||
|
stable_paths: &[ #(#versioned),* ],
|
||||||
|
deprecated: #deprecated,
|
||||||
|
removed: #removed,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
// Unused variants will be constructed when the macro input is updated
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum HistoryEntry {
|
||||||
|
Unstable { path: EndpointPath },
|
||||||
|
Stable { version: MatrixVersionLiteral, path: EndpointPath },
|
||||||
|
Deprecated { version: MatrixVersionLiteral },
|
||||||
|
Removed { version: MatrixVersionLiteral },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HistoryEntry {
|
||||||
|
pub(super) fn path(&self) -> Option<&EndpointPath> {
|
||||||
|
Some(match self {
|
||||||
|
HistoryEntry::Stable { version: _, path } => path,
|
||||||
|
HistoryEntry::Unstable { path } => path,
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct EndpointPath(LitStr);
|
pub struct EndpointPath(LitStr);
|
||||||
|
|
||||||
impl EndpointPath {
|
impl EndpointPath {
|
||||||
pub fn value(&self) -> String {
|
pub fn value(&self) -> String {
|
||||||
self.0.value()
|
self.0.value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn args(&self) -> Vec<String> {
|
||||||
|
self.value().split('/').filter_map(|s| s.strip_prefix(':')).map(String::from).collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for EndpointPath {
|
impl Parse for EndpointPath {
|
||||||
@ -328,7 +429,7 @@ impl Parse for EndpointPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for EndpointPath {
|
impl ToTokens for EndpointPath {
|
||||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
self.0.to_tokens(tokens);
|
self.0.to_tokens(tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,19 @@ use proc_macro2::TokenStream;
|
|||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::{parse::Parse, Error, LitFloat};
|
use syn::{parse::Parse, Error, LitFloat};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct MatrixVersionLiteral {
|
pub struct MatrixVersionLiteral {
|
||||||
pub(crate) major: NonZeroU8,
|
pub(crate) major: NonZeroU8,
|
||||||
pub(crate) minor: u8,
|
pub(crate) minor: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ONE: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(1) };
|
||||||
|
|
||||||
|
impl MatrixVersionLiteral {
|
||||||
|
pub const V1_0: Self = Self { major: ONE, minor: 0 };
|
||||||
|
pub const V1_1: Self = Self { major: ONE, minor: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
impl Parse for MatrixVersionLiteral {
|
impl Parse for MatrixVersionLiteral {
|
||||||
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
|
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
|
||||||
let fl: LitFloat = input.parse()?;
|
let fl: LitFloat = input.parse()?;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user