From b9ec4db8f088e582ecc6e1e546f6d493f028e763 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 26 Oct 2022 19:12:19 +0200 Subject: [PATCH] api: Add metadata! macro for easy Metadata construction Co-authored-by: Jonathan de Jong --- crates/ruma-common/src/api.rs | 77 ++++++++++++++++++++++++++ crates/ruma-common/src/api/metadata.rs | 51 ++++++++++++++++- crates/ruma/src/lib.rs | 5 +- 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/crates/ruma-common/src/api.rs b/crates/ruma-common/src/api.rs index 6174d083..6da8e033 100644 --- a/crates/ruma-common/src/api.rs +++ b/crates/ruma-common/src/api.rs @@ -428,3 +428,80 @@ pub enum AuthScheme { /// as defined in the federation API. ServerSignatures, } + +/// Convenient constructor for [`Metadata`] constants. +/// +/// Usage: +/// +/// ``` +/// # use ruma_common::{metadata, api::Metadata}; +/// const _: Metadata = metadata! { +/// description: "Endpoint description.", +/// method: GET, // one of the associated constants of http::Method +/// name: "enpdoint_name", +/// rate_limited: true, +/// authentication: AccessToken, // one of the variants of api::AuthScheme +/// +/// // history of endpoint paths +/// // there must be at least one path but otherwise everything is optional +/// history: { +/// unstable => "/_matrix/foo/org.bar.msc9000/baz", +/// unstable => "/_matrix/foo/org.bar.msc9000/qux", +/// 1.0 => "/_matrix/media/r0/qux", +/// 1.1 => "/_matrix/media/v3/qux", +/// 1.2 => deprecated, +/// 1.3 => removed, +/// } +/// }; +/// ``` +#[macro_export] +macro_rules! metadata { + ( $( $field:ident: $rhs:tt ),+ $(,)? ) => { + $crate::api::Metadata { + $( $field: $crate::metadata!(@field $field: $rhs) ),+ + } + }; + + ( @field method: $method:ident ) => { $crate::exports::http::Method::$method }; + + ( @field authentication: $scheme:ident ) => { $crate::api::AuthScheme::$scheme }; + + ( @field history: { + $( unstable => $unstable_path:literal ),* + $(, $( $version:literal => $rhs:tt ),+ )? + $(,)? + } ) => { + $crate::metadata! { + @history_impl + [ $($unstable_path),* ] + // Flip left and right to avoid macro parsing ambiguities + $( $( $rhs = $version ),+ )? + } + }; + + // Simple literal case: used for description, name, rate_limited + ( @field $_field:ident: $rhs:literal ) => { $rhs }; + + ( @history_impl + [ $($unstable_path:literal),* ] + $( + $( $stable_path:literal = $version:literal ),+ + $(, + deprecated = $deprecated_version:literal + $(, removed = $removed_version:literal )? + )? + )? + ) => { + $crate::api::VersionHistory::new( + &[ $( $unstable_path ),+ ], + &[ $($( + ($crate::api::MatrixVersion::from_lit(stringify!($version)), $stable_path) + ),+)? ], + $crate::metadata!(@optional_version $($( $deprecated_version )?)?), + $crate::metadata!(@optional_version $($($( $removed_version )?)?)?), + ) + }; + + ( @optional_version ) => { None }; + ( @optional_version $version:literal ) => { Some($crate::api::MatrixVersion::from_lit(stringify!($version))) } +} diff --git a/crates/ruma-common/src/api/metadata.rs b/crates/ruma-common/src/api/metadata.rs index 70f35a9d..e56daaf9 100644 --- a/crates/ruma-common/src/api/metadata.rs +++ b/crates/ruma-common/src/api/metadata.rs @@ -559,7 +559,7 @@ impl MatrixVersion { } /// Try to turn a pair of (major, minor) version components back into a `MatrixVersion`. - pub fn from_parts(major: u8, minor: u8) -> Result { + pub const fn from_parts(major: u8, minor: u8) -> Result { match (major, minor) { (1, 0) => Ok(MatrixVersion::V1_0), (1, 1) => Ok(MatrixVersion::V1_1), @@ -570,6 +570,48 @@ impl MatrixVersion { } } + /// Constructor for use by the `metadata!` macro. + /// + /// Accepts string literals and parses them. + #[doc(hidden)] + pub const fn from_lit(lit: &'static str) -> Self { + use konst::{option, primitive::parse_u8, result, string}; + + let major: u8; + let minor: u8; + + let mut lit_iter = string::split(lit, ".").next(); + + { + let (checked_first, checked_split) = option::unwrap!(lit_iter); // First iteration always succeeds + + major = result::unwrap_or_else!(parse_u8(checked_first), |_| panic!( + "major version is not a valid number" + )); + + lit_iter = checked_split.next(); + } + + match lit_iter { + Some((checked_second, checked_split)) => { + minor = result::unwrap_or_else!(parse_u8(checked_second), |_| panic!( + "minor version is not a valid number" + )); + + lit_iter = checked_split.next(); + } + None => panic!("could not find dot to denote second number"), + } + + if lit_iter.is_some() { + panic!("version literal contains more than one dot") + } + + result::unwrap_or_else!(Self::from_parts(major, minor), |_| panic!( + "not a valid version literal" + )) + } + // Internal function to do ordering in const-fn contexts const fn const_ord(&self, other: &Self) -> Ordering { let self_parts = self.into_parts(); @@ -722,4 +764,11 @@ mod tests { Err(IntoHttpError::NoUnstablePath) ); } + + #[test] + fn version_literal() { + const LIT: MatrixVersion = MatrixVersion::from_lit("1.0"); + + assert_eq!(LIT, V1_0); + } } diff --git a/crates/ruma/src/lib.rs b/crates/ruma/src/lib.rs index 31272755..091669c6 100644 --- a/crates/ruma/src/lib.rs +++ b/crates/ruma/src/lib.rs @@ -95,7 +95,10 @@ pub use ruma_state_res as state_res; /// [apis]: https://spec.matrix.org/v1.4/#matrix-apis #[cfg(feature = "api")] pub mod api { - pub use ruma_common::api::*; + // The metadata macro is also exported at the crate root because `#[macro_export]` always + // places things at the crate root of the defining crate and we do a glob re-export of + // `ruma_common`, but here is the more logical (preferred) location. + pub use ruma_common::{api::*, metadata}; #[cfg(any(feature = "appservice-api-c", feature = "appservice-api-s"))] #[doc(inline)]