mod garde;
mod schemars;
mod serde;
mod utoipa;
mod validator;
pub struct Context<'a> {
pub names: &'a crate::conventions::FiltersNames,
pub runtime: &'a syn::Path,
pub flag: &'a syn::Ident,
}
pub struct NonNullableContext<'a> {
pub ident: &'a syn::Ident,
pub fields: &'a [syn::Ident],
pub runtime: &'a syn::Path,
pub flag: &'a syn::Ident,
}
#[derive(Default)]
pub struct TypeAdditions {
pub derives: Vec<proc_macro2::TokenStream>,
pub attributes: proc_macro2::TokenStream,
}
pub trait Integration: Sync {
fn name(&self) -> &'static str;
fn requires(&self) -> &'static [(&'static str, &'static str)] {
&[]
}
fn names_api_runtime(&self) -> bool {
false
}
fn sort_direction(&self, _context: &Context<'_>) -> Option<proc_macro2::TokenStream> {
None
}
fn page(&self, _context: &Context<'_>) -> Option<proc_macro2::TokenStream> {
None
}
fn sort_field(&self, _context: &Context<'_>) -> TypeAdditions {
TypeAdditions::default()
}
fn sort_details(&self, _context: &Context<'_>) -> TypeAdditions {
TypeAdditions::default()
}
fn query_params(&self, _context: &Context<'_>) -> TypeAdditions {
TypeAdditions::default()
}
fn page_size(&self, _context: &Context<'_>) -> proc_macro2::TokenStream {
proc_macro2::TokenStream::new()
}
fn sort(&self, _context: &Context<'_>) -> proc_macro2::TokenStream {
proc_macro2::TokenStream::new()
}
fn items(&self, _context: &Context<'_>) -> proc_macro2::TokenStream {
proc_macro2::TokenStream::new()
}
fn sortable_items(&self, _context: &Context<'_>) -> proc_macro2::TokenStream {
proc_macro2::TokenStream::new()
}
fn supports_validate_non_nullable(&self) -> bool {
false
}
fn validate_non_nullable(&self, _context: &NonNullableContext<'_>) -> proc_macro2::TokenStream {
proc_macro2::TokenStream::new()
}
}
pub const BUILT_IN: [&dyn Integration; 5] = [
&serde::Serde,
&utoipa::Utoipa,
&validator::Validator,
&schemars::Schemars,
&garde::Garde,
];
#[derive(Clone)]
pub(crate) struct Active {
pub integration: &'static dyn Integration,
pub flag: syn::Ident,
}
impl std::fmt::Debug for Active {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.debug_tuple("Active").field(&self.flag).finish()
}
}
impl Active {
pub(crate) const fn context<'a>(
&'a self,
names: &'a crate::conventions::FiltersNames,
runtime: &'a syn::Path,
) -> Context<'a> {
Context {
names,
runtime,
flag: &self.flag,
}
}
}
fn api_feature_check(context: &Context<'_>, feature: &str) -> proc_macro2::TokenStream {
let span = context.flag.span();
let runtime = crate::utils::respanned(context.runtime, span);
let feature = syn::Ident::new(feature, span);
quote::quote_spanned! {span=>
const _: () = {
const fn requires_feature<T: #runtime::features::#feature>() {}
requires_feature::<#runtime::SortDirectionDto>();
};
}
}
impl PartialEq for Active {
fn eq(&self, other: &Self) -> bool {
self.integration.name() == other.integration.name() && self.flag == other.flag
}
}
impl Eq for Active {}
pub(crate) fn check_registry(registry: &[&'static dyn Integration]) -> syn::Result<()> {
for (index, integration) in registry.iter().enumerate() {
let name = integration.name();
let clashes = name == "mirrors"
|| registry
.iter()
.take(index)
.any(|earlier| earlier.name() == name);
if clashes {
return crate::errors::abort_call_site(
&format!("The conventions register a second `{name}` option of #[filters(...)]"),
"Give each integration of `Conventions::integrations` its own name, other than `mirrors`",
);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
struct Named(&'static str);
impl super::Integration for Named {
fn name(&self) -> &'static str {
self.0
}
}
static OTHER_SERDE: Named = Named("serde");
static MIRRORS: Named = Named("mirrors");
static GRAPHQL: Named = Named("graphql");
#[test]
fn test_check_registry_accepts_distinct_names() {
assert!(super::check_registry(&super::BUILT_IN).is_ok());
assert!(super::check_registry(&[super::BUILT_IN[0], &GRAPHQL]).is_ok());
}
#[test]
fn test_check_registry_rejects_clashing_names() {
assert!(super::check_registry(&[super::BUILT_IN[0], &OTHER_SERDE]).is_err());
assert!(super::check_registry(&[&MIRRORS]).is_err());
}
#[test]
fn test_requirements_name_built_in_integrations() {
for integration in super::BUILT_IN {
for (required, _) in integration.requires() {
assert!(
super::BUILT_IN
.iter()
.any(|other| other.name() == *required),
"{required}"
);
}
}
}
}