diff --git a/crates/ruma-common/src/api/metadata.rs b/crates/ruma-common/src/api/metadata.rs index 2a202c4f..d50a8fcc 100644 --- a/crates/ruma-common/src/api/metadata.rs +++ b/crates/ruma-common/src/api/metadata.rs @@ -124,6 +124,13 @@ impl Metadata { 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 diff --git a/crates/ruma-macros/src/api.rs b/crates/ruma-macros/src/api.rs index 2c478291..e0a2e774 100644 --- a/crates/ruma-macros/src/api.rs +++ b/crates/ruma-macros/src/api.rs @@ -12,12 +12,7 @@ use syn::{ Attribute, Field, Token, Type, }; -use self::{ - api_metadata::Metadata, - api_request::Request, - api_response::Response, - request::{RequestField, RequestFieldKind}, -}; +use self::{api_metadata::Metadata, api_request::Request, api_response::Response}; use crate::util::import_ruma_common; mod api_metadata; @@ -56,7 +51,6 @@ pub struct Api { impl Api { pub fn expand_all(self) -> TokenStream { 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(); @@ -80,7 +74,6 @@ impl Api { quote! { #maybe_feature_error - #maybe_path_error // For some reason inlining the expression causes issues with macro parsing const _RUMA_API_VERSION_HISTORY: #ruma_common::api::VersionHistory = #history; @@ -102,39 +95,6 @@ impl Api { 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::>()?; - - 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 { diff --git a/crates/ruma-macros/src/api/api_metadata.rs b/crates/ruma-macros/src/api/api_metadata.rs index d8110722..7cb95aa1 100644 --- a/crates/ruma-macros/src/api/api_metadata.rs +++ b/crates/ruma-macros/src/api/api_metadata.rs @@ -389,17 +389,6 @@ pub enum HistoryEntry { 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)] pub struct EndpointPath(LitStr); @@ -407,10 +396,6 @@ impl EndpointPath { pub fn value(&self) -> String { self.0.value() } - - pub fn args(&self) -> Vec { - self.value().split('/').filter_map(|s| s.strip_prefix(':')).map(String::from).collect() - } } impl Parse for EndpointPath { diff --git a/crates/ruma-macros/src/api/request.rs b/crates/ruma-macros/src/api/request.rs index 0bc828cf..72083042 100644 --- a/crates/ruma-macros/src/api/request.rs +++ b/crates/ruma-macros/src/api/request.rs @@ -238,7 +238,7 @@ impl Request { } } - pub(super) fn check(&self, ruma_common: &TokenStream) -> syn::Result> { + pub(super) fn check(&self, ruma_common: &TokenStream) -> syn::Result { let http = quote! { #ruma_common::exports::http }; // TODO: highlight problematic fields @@ -297,8 +297,21 @@ impl Request { )); } - Ok((has_body_fields || has_newtype_body_field).then(|| { - quote! { + let path_fields = self.path_fields().map(|f| f.ident.as_ref().unwrap().to_string()); + 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] fn request_is_not_get() { ::std::assert_ne!( @@ -306,8 +319,10 @@ impl Request { "GET endpoints can't have body fields", ); } - } - })) + }); + } + + Ok(tests) } }