api: Add added
, deprecated
, and removed
metadata fields
This commit is contained in:
parent
2de652049d
commit
0dcdb57c29
@ -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
|
||||
};
|
||||
|
@ -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()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ mod auth_scheme;
|
||||
mod request;
|
||||
mod response;
|
||||
mod util;
|
||||
mod version;
|
||||
|
||||
use api::Api;
|
||||
use request::expand_derive_request;
|
||||
|
@ -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))
|
||||
}
|
||||
|
46
crates/ruma-api-macros/src/version.rs
Normal file
46
crates/ruma-api-macros/src/version.rs
Normal 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 });
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
23
crates/ruma-api/tests/ui/08-deprecated-without-added.rs
Normal file
23
crates/ruma-api/tests/ui/08-deprecated-without-added.rs
Normal 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() {}
|
13
crates/ruma-api/tests/ui/08-deprecated-without-added.stderr
Normal file
13
crates/ruma-api/tests/ui/08-deprecated-without-added.stderr
Normal 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)
|
@ -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() {}
|
23
crates/ruma-api/tests/ui/09-removed-without-deprecated.rs
Normal file
23
crates/ruma-api/tests/ui/09-removed-without-deprecated.rs
Normal 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() {}
|
@ -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)
|
Loading…
x
Reference in New Issue
Block a user