api: Add added, deprecated, and removed metadata fields

This commit is contained in:
Jonathan de Jong 2022-02-08 10:52:43 +01:00 committed by GitHub
parent 2de652049d
commit 0dcdb57c29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 256 additions and 6 deletions

View File

@ -60,8 +60,7 @@ impl Api {
}
})
.collect();
let authentication: TokenStream = self
.metadata
let authentication: TokenStream = metadata
.authentication
.iter()
.map(|r| {
@ -73,6 +72,9 @@ impl Api {
}
})
.collect();
let added = util::map_option_literal(&metadata.added);
let deprecated = util::map_option_literal(&metadata.deprecated);
let removed = util::map_option_literal(&metadata.removed);
let error_ty = self
.error_ty
@ -90,6 +92,9 @@ impl Api {
method: #http::Method::#method,
name: #name,
path: #path,
added: #added,
deprecated: #deprecated,
removed: #removed,
#rate_limited
#authentication
};

View File

@ -7,7 +7,7 @@ use syn::{
Attribute, Ident, LitBool, LitStr, Token,
};
use crate::{auth_scheme::AuthScheme, util};
use crate::{auth_scheme::AuthScheme, util, version::MatrixVersionLiteral};
mod kw {
syn::custom_keyword!(metadata);
@ -17,6 +17,9 @@ mod kw {
syn::custom_keyword!(path);
syn::custom_keyword!(rate_limited);
syn::custom_keyword!(authentication);
syn::custom_keyword!(added);
syn::custom_keyword!(deprecated);
syn::custom_keyword!(removed);
}
/// A field of Metadata that contains attribute macros
@ -47,6 +50,15 @@ pub struct Metadata {
/// The authentication field.
pub authentication: Vec<MetadataField<AuthScheme>>,
/// The added field.
pub added: Option<MatrixVersionLiteral>,
/// 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<()> {
@ -80,6 +92,9 @@ impl Parse for Metadata {
let mut path = None;
let mut rate_limited = vec![];
let mut authentication = vec![];
let mut added = None;
let mut deprecated = None;
let mut removed = None;
for field_value in field_values {
match field_value {
@ -93,12 +108,39 @@ impl Parse for Metadata {
FieldValue::Authentication(value, attrs) => {
authentication.push(MetadataField { attrs, value });
}
FieldValue::Added(v) => set_field(&mut added, v)?,
FieldValue::Deprecated(v) => set_field(&mut deprecated, v)?,
FieldValue::Removed(v) => set_field(&mut removed, v)?,
}
}
let missing_field =
|name| syn::Error::new_spanned(metadata_kw, format!("missing field `{}`", name));
if let Some(deprecated) = &deprecated {
if added.is_none() {
return Err(syn::Error::new_spanned(
deprecated,
"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
// having a deprecation version inbetween, but that would not be allowed by their own
// deprecation policy, so lets just assume there's always a deprecation version before a
// removal one.
//
// If matrix does so anyways, we can just alter this.
if let Some(removed) = &removed {
if deprecated.is_none() {
return Err(syn::Error::new_spanned(
removed,
"removed version is defined while deprecated version is not defined",
));
}
}
Ok(Self {
description: description.ok_or_else(|| missing_field("description"))?,
method: method.ok_or_else(|| missing_field("method"))?,
@ -114,6 +156,9 @@ impl Parse for Metadata {
} else {
authentication
},
added,
deprecated,
removed,
})
}
}
@ -125,6 +170,9 @@ enum Field {
Path,
RateLimited,
Authentication,
Added,
Deprecated,
Removed,
}
impl Parse for Field {
@ -149,6 +197,15 @@ impl Parse for Field {
} else if lookahead.peek(kw::authentication) {
let _: kw::authentication = input.parse()?;
Ok(Self::Authentication)
} else if lookahead.peek(kw::added) {
let _: kw::added = input.parse()?;
Ok(Self::Added)
} else if lookahead.peek(kw::deprecated) {
let _: kw::deprecated = input.parse()?;
Ok(Self::Deprecated)
} else if lookahead.peek(kw::removed) {
let _: kw::removed = input.parse()?;
Ok(Self::Removed)
} else {
Err(lookahead.error())
}
@ -162,6 +219,9 @@ enum FieldValue {
Path(LitStr),
RateLimited(LitBool, Vec<Attribute>),
Authentication(AuthScheme, Vec<Attribute>),
Added(MatrixVersionLiteral),
Deprecated(MatrixVersionLiteral),
Removed(MatrixVersionLiteral),
}
impl Parse for FieldValue {
@ -196,6 +256,9 @@ impl Parse for FieldValue {
}
Field::RateLimited => Self::RateLimited(input.parse()?, attrs),
Field::Authentication => Self::Authentication(input.parse()?, attrs),
Field::Added => Self::Added(input.parse()?),
Field::Deprecated => Self::Deprecated(input.parse()?),
Field::Removed => Self::Removed(input.parse()?),
})
}
}

View File

@ -19,6 +19,7 @@ mod auth_scheme;
mod request;
mod response;
mod util;
mod version;
use api::Api;
use request::expand_derive_request;

View File

@ -4,7 +4,7 @@ use std::collections::BTreeSet;
use proc_macro2::TokenStream;
use proc_macro_crate::{crate_name, FoundCrate};
use quote::{format_ident, quote};
use quote::{format_ident, quote, ToTokens};
use syn::{parse_quote, visit::Visit, AttrStyle, Attribute, Lifetime, NestedMeta, Type};
pub fn import_ruma_api() -> TokenStream {
@ -25,6 +25,13 @@ pub fn import_ruma_api() -> TokenStream {
}
}
pub fn map_option_literal<T: ToTokens>(ver: &Option<T>) -> TokenStream {
match ver {
Some(v) => quote! { ::std::option::Option::Some(#v) },
None => quote! { ::std::option::Option::None },
}
}
pub fn is_valid_endpoint_path(string: &str) -> bool {
string.as_bytes().iter().all(|b| (0x21..=0x7E).contains(b))
}

View File

@ -0,0 +1,46 @@
use std::{convert::TryInto, num::NonZeroU8};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{parse::Parse, Error, LitFloat};
#[derive(Clone)]
pub struct MatrixVersionLiteral {
major: NonZeroU8,
minor: u8,
}
impl Parse for MatrixVersionLiteral {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let fl: LitFloat = input.parse()?;
if !fl.suffix().is_empty() {
return Err(Error::new_spanned(
fl,
"matrix version has to be only two positive numbers separated by a `.`",
));
}
let ver_vec: Vec<String> = fl.to_string().split('.').map(&str::to_owned).collect();
let ver: [String; 2] = ver_vec.try_into().map_err(|_| {
Error::new_spanned(&fl, "did not contain only both an X and Y value like X.Y")
})?;
let major: NonZeroU8 = ver[0].parse().map_err(|e| {
Error::new_spanned(&fl, format!("major number failed to parse as >0 number: {}", e))
})?;
let minor: u8 = ver[1]
.parse()
.map_err(|e| Error::new_spanned(&fl, format!("minor number failed to parse: {}", e)))?;
Ok(Self { major, minor })
}
}
impl ToTokens for MatrixVersionLiteral {
fn to_tokens(&self, tokens: &mut TokenStream) {
let variant = format_ident!("V{}_{}", u8::from(self.major), self.minor);
tokens.extend(quote! { ::ruma_api::MatrixVersion::#variant });
}
}

View File

@ -424,6 +424,25 @@ pub struct Metadata {
/// What authentication scheme the server uses for this endpoint.
pub authentication: AuthScheme,
/// The matrix version that this endpoint was added in.
///
/// 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.
// TODO add once try_into_http_request has been altered;
// This will make `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.
// TODO add once try_into_http_request has been altered;
// This will make `try_into_http_request` emit an error,
// see the corresponding documentation for more information.
pub removed: Option<MatrixVersion>,
}
/// The Matrix versions Ruma currently understands to exist.

View File

@ -31,6 +31,9 @@ const METADATA: Metadata = Metadata {
path: "/_matrix/client/r0/directory/room/:room_alias",
rate_limited: false,
authentication: AuthScheme::None,
added: None,
deprecated: None,
removed: None,
};
impl OutgoingRequest for Request {

View File

@ -8,4 +8,6 @@ fn ui() {
t.pass("tests/ui/05-request-only.rs");
t.pass("tests/ui/06-response-only.rs");
t.compile_fail("tests/ui/07-error-type-attribute.rs");
t.compile_fail("tests/ui/08-deprecated-without-added.rs");
t.compile_fail("tests/ui/09-removed-without-deprecated.rs");
}

View File

@ -1,6 +1,6 @@
use ruma_api::ruma_api;
use ruma_serde::Raw;
use ruma_events::{tag::TagEvent, AnyRoomEvent};
use ruma_serde::Raw;
ruma_api! {
metadata: {
@ -10,6 +10,9 @@ ruma_api! {
path: "/_matrix/some/endpoint/:baz",
rate_limited: false,
authentication: None,
added: 1.0,
deprecated: 1.1,
removed: 1.2,
}
request: {
@ -50,4 +53,10 @@ ruma_api! {
}
}
fn main() {}
fn main() {
use ruma_api::MatrixVersion;
assert_eq!(METADATA.added, Some(MatrixVersion::V1_0));
assert_eq!(METADATA.deprecated, Some(MatrixVersion::V1_1));
assert_eq!(METADATA.removed, Some(MatrixVersion::V1_2));
}

View File

@ -0,0 +1,23 @@
use ruma_api::ruma_api;
ruma_api! {
metadata: {
description: "This will fail.",
method: GET,
name: "invalid_versions",
path: "/a/path",
rate_limited: false,
authentication: None,
deprecated: 1.1,
}
request: {
#[ruma_api(query_map)]
pub fields: Vec<(String, String)>,
}
response: {}
}
fn main() {}

View File

@ -0,0 +1,13 @@
error: deprecated version is defined while added version is not defined
--> tests/ui/08-deprecated-without-added.rs:3:1
|
3 | / ruma_api! {
4 | | metadata: {
5 | | description: "This will fail.",
6 | | method: GET,
... |
20 | | response: {}
21 | | }
| |_^
|
= note: this error originates in the macro `ruma_api` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -0,0 +1,23 @@
use ruma_api::ruma_api;
ruma_api! {
metadata: {
description: "This will fail.",
method: GET,
name: "invalid_versions",
path: "/a/path",
rate_limited: false,
authentication: None,
removed: 1.1,
}
request: {
#[ruma_api(query_map)]
pub fields: Vec<(String, String)>,
}
response: {}
}
fn main() {}

View File

@ -0,0 +1,23 @@
use ruma_api::ruma_api;
ruma_api! {
metadata: {
description: "This will fail.",
method: GET,
name: "invalid_versions",
path: "/a/path",
rate_limited: false,
authentication: None,
removed: 1.1,
}
request: {
#[ruma_api(query_map)]
pub fields: Vec<(String, String)>,
}
response: {}
}
fn main() {}

View File

@ -0,0 +1,13 @@
error: removed version is defined while deprecated version is not defined
--> tests/ui/09-removed-without-deprecated.rs:3:1
|
3 | / ruma_api! {
4 | | metadata: {
5 | | description: "This will fail.",
6 | | method: GET,
... |
20 | | response: {}
21 | | }
| |_^
|
= note: this error originates in the macro `ruma_api` (in Nightly builds, run with -Z macro-backtrace for more info)