use quote::quote;
pub struct Validator;
impl super::Integration for Validator {
fn name(&self) -> &'static str {
"validator"
}
fn requires(&self) -> &'static [(&'static str, &'static str)] {
&[(
"serde",
"a validation error carries the rejected value, serialized",
)]
}
fn supports_validate_non_nullable(&self) -> bool {
true
}
fn validate_non_nullable(
&self,
context: &super::NonNullableContext<'_>,
) -> proc_macro2::TokenStream {
let super::NonNullableContext {
ident,
fields,
flag,
..
} = *context;
let names = fields
.iter()
.map(|field| syn::LitStr::new(&field.to_string(), field.span()));
let messages = fields.iter().map(|field| {
format!(
"{} cannot be set to null",
crate::utils::uppercase_first_letter(&field.to_string())
)
});
let validate_check = quote::quote_spanned! {flag.span()=>
const _: fn() = || {
fn validator_validate<T: validator::Validate>() {}
validator_validate::<#ident>();
};
};
quote! {
#validate_check
impl #ident {
pub fn validate_non_nullable(&self) -> Result<(), validator::ValidationErrors> {
validator::Validate::validate(&self)?;
let mut errors = validator::ValidationErrors::new();
for field in self.rejected_nulls() {
let message: &'static str = match field {
#(#names => #messages,)*
_ => "This field cannot be set to null",
};
errors.add(
field,
validator::ValidationError::new("non-nullable").with_message(message.into()),
);
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
}
}
fn query_params(&self, _context: &super::Context<'_>) -> super::TypeAdditions {
super::TypeAdditions {
derives: vec![quote!(validator::Validate)],
attributes: quote!(),
}
}
fn page_size(&self, _context: &super::Context<'_>) -> proc_macro2::TokenStream {
quote!(#[validate(range(min = 1))])
}
fn sort(&self, context: &super::Context<'_>) -> proc_macro2::TokenStream {
let crate::conventions::FiltersNames {
sort_field,
sort_details,
..
} = context.names;
let validate_sort = syn::LitStr::new(
&format!("{sort_details}::validate_sort"),
proc_macro2::Span::call_site(),
);
quote! {
#[validate(length(min = 1, max = #sort_field::COUNT))]
#[validate(custom(function = #validate_sort))]
}
}
fn sortable_items(&self, context: &super::Context<'_>) -> proc_macro2::TokenStream {
let crate::conventions::FiltersNames {
sort_field,
sort_details,
..
} = context.names;
quote! {
impl #sort_details {
pub fn validate_sort(sort: &[Self]) -> Result<(), validator::ValidationError> {
let fields: std::collections::HashSet<#sort_field> = sort.iter().map(|details| details.field).collect();
if sort.len() == fields.len() {
Ok(())
} else {
let mut err = validator::ValidationError::new("sort");
err.message = Some("Duplicate sort field".into());
return Err(err);
}
}
}
}
}
}