api: Add metadata! macro for easy Metadata construction

Co-authored-by: Jonathan de Jong <jonathan@automatia.nl>
This commit is contained in:
Jonas Platte 2022-10-26 19:12:19 +02:00 committed by Jonas Platte
parent ec67fcbd6f
commit b9ec4db8f0
3 changed files with 131 additions and 2 deletions

View File

@ -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))) }
}

View File

@ -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<Self, UnknownVersionError> {
pub const fn from_parts(major: u8, minor: u8) -> Result<Self, UnknownVersionError> {
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);
}
}

View File

@ -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)]