api: Replace ruma_api! compile-time path check with a test

This commit is contained in:
Jonas Platte 2022-11-10 11:00:03 +01:00
parent 9195a5de18
commit 2451f33a64
No known key found for this signature in database
GPG Key ID: AAA7A61F696C3E0C
4 changed files with 28 additions and 61 deletions

View File

@ -124,6 +124,13 @@ impl Metadata {
Ok(res) Ok(res)
} }
// Used for generated `#[test]`s
#[doc(hidden)]
pub fn _path_parameters(&self) -> Vec<&'static str> {
let path = self.history.all_paths().next().unwrap();
path.split('/').filter_map(|segment| segment.strip_prefix(':')).collect()
}
} }
/// The complete history of this endpoint as far as Ruma knows, together with all variants on /// The complete history of this endpoint as far as Ruma knows, together with all variants on

View File

@ -12,12 +12,7 @@ use syn::{
Attribute, Field, Token, Type, Attribute, Field, Token, Type,
}; };
use self::{ use self::{api_metadata::Metadata, api_request::Request, api_response::Response};
api_metadata::Metadata,
api_request::Request,
api_response::Response,
request::{RequestField, RequestFieldKind},
};
use crate::util::import_ruma_common; use crate::util::import_ruma_common;
mod api_metadata; mod api_metadata;
@ -56,7 +51,6 @@ pub struct Api {
impl Api { impl Api {
pub fn expand_all(self) -> TokenStream { pub fn expand_all(self) -> TokenStream {
let maybe_feature_error = ensure_feature_presence().map(syn::Error::to_compile_error); let maybe_feature_error = ensure_feature_presence().map(syn::Error::to_compile_error);
let maybe_path_error = self.check_paths().err().map(syn::Error::into_compile_error);
let ruma_common = import_ruma_common(); let ruma_common = import_ruma_common();
@ -80,7 +74,6 @@ impl Api {
quote! { quote! {
#maybe_feature_error #maybe_feature_error
#maybe_path_error
// For some reason inlining the expression causes issues with macro parsing // For some reason inlining the expression causes issues with macro parsing
const _RUMA_API_VERSION_HISTORY: #ruma_common::api::VersionHistory = #history; const _RUMA_API_VERSION_HISTORY: #ruma_common::api::VersionHistory = #history;
@ -102,39 +95,6 @@ impl Api {
type _SilenceUnusedError = #error_ty; type _SilenceUnusedError = #error_ty;
} }
} }
fn check_paths(&self) -> syn::Result<()> {
let mut path_iter = self.metadata.history.entries.iter().filter_map(|entry| entry.path());
let path = path_iter.next().ok_or_else(|| {
syn::Error::new(Span::call_site(), "at least one path metadata field must be set")
})?;
let path_args = path.args();
if let Some(req) = &self.request {
let path_field_names: Vec<_> = req
.fields
.iter()
.cloned()
.filter_map(|f| match RequestField::try_from(f) {
Ok(RequestField { kind: RequestFieldKind::Path, inner }) => {
Some(Ok(inner.ident.unwrap().to_string()))
}
Ok(_) => None,
Err(e) => Some(Err(e)),
})
.collect::<syn::Result<_>>()?;
if path_args != path_field_names {
return Err(syn::Error::new_spanned(
req.request_kw,
"path fields must be in the same order as they appear in the path segments",
));
}
}
Ok(())
}
} }
impl Parse for Api { impl Parse for Api {

View File

@ -389,17 +389,6 @@ pub enum HistoryEntry {
Removed { 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)] #[derive(Clone, Debug, PartialEq)]
pub struct EndpointPath(LitStr); pub struct EndpointPath(LitStr);
@ -407,10 +396,6 @@ 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 {

View File

@ -238,7 +238,7 @@ impl Request {
} }
} }
pub(super) fn check(&self, ruma_common: &TokenStream) -> syn::Result<Option<TokenStream>> { pub(super) fn check(&self, ruma_common: &TokenStream) -> syn::Result<TokenStream> {
let http = quote! { #ruma_common::exports::http }; let http = quote! { #ruma_common::exports::http };
// TODO: highlight problematic fields // TODO: highlight problematic fields
@ -297,8 +297,21 @@ impl Request {
)); ));
} }
Ok((has_body_fields || has_newtype_body_field).then(|| { let path_fields = self.path_fields().map(|f| f.ident.as_ref().unwrap().to_string());
quote! { let mut tests = quote! {
#[::std::prelude::v1::test]
fn path_parameters() {
let path_params = METADATA._path_parameters();
let request_path_fields: &[&::std::primitive::str] = &[#(#path_fields),*];
::std::assert_eq!(
path_params, request_path_fields,
"Path parameters must match the `Request`'s `#[ruma_api(path)]` fields"
);
}
};
if has_body_fields || has_newtype_body_field {
tests.extend(quote! {
#[::std::prelude::v1::test] #[::std::prelude::v1::test]
fn request_is_not_get() { fn request_is_not_get() {
::std::assert_ne!( ::std::assert_ne!(
@ -306,8 +319,10 @@ impl Request {
"GET endpoints can't have body fields", "GET endpoints can't have body fields",
); );
} }
} });
})) }
Ok(tests)
} }
} }