use proc_macro2::{Ident, TokenStream};
use prost_reflect::{DescriptorPool, FieldDescriptor, Kind};
use quote::quote;
use prost_protovalidate_types::rules_meta::repeated as meta;
use prost_protovalidate_types::{Ignore, RepeatedRules};
use crate::Error;
use crate::codegen;
use crate::naming::NamingContext;
use crate::rules;
fn emit_min_items_check(field_ident: &Ident, proto_name: &str, min: u64) -> TokenStream {
#[allow(clippy::cast_possible_truncation)]
let min_usize = min as usize;
let rule_id = meta::MIN_ITEMS_ID;
let msg = meta::min_items_message(min);
quote! {
if self.#field_ident.len() < #min_usize {
violations.push(::prost_protovalidate::Violation::new(
#proto_name, #rule_id, #msg,
));
}
}
}
fn emit_max_items_check(field_ident: &Ident, proto_name: &str, max: u64) -> TokenStream {
#[allow(clippy::cast_possible_truncation)]
let max_usize = max as usize;
let rule_id = meta::MAX_ITEMS_ID;
let msg = meta::max_items_message(max);
quote! {
if self.#field_ident.len() > #max_usize {
violations.push(::prost_protovalidate::Violation::new(
#proto_name, #rule_id, #msg,
));
}
}
}
fn emit_canonical_bits_unique_check(
field_ident: &Ident,
proto_name: &str,
bits_ty: &TokenStream,
canonical_fn: &TokenStream,
) -> TokenStream {
let rule_id = meta::UNIQUE_ID;
let msg = meta::UNIQUE_MESSAGE;
quote! {
{
let mut _seen = ::std::collections::HashSet::<#bits_ty>::new();
for item in &self.#field_ident {
let Some(_bits) = #canonical_fn(*item) else {
continue;
};
if !_seen.insert(_bits) {
violations.push(::prost_protovalidate::Violation::new(
#proto_name, #rule_id, #msg,
));
break;
}
}
}
}
}
fn emit_generic_unique_check(field_ident: &Ident, proto_name: &str) -> TokenStream {
let rule_id = meta::UNIQUE_ID;
let msg = meta::UNIQUE_MESSAGE;
quote! {
{
let mut _seen = ::std::collections::HashSet::new();
for item in &self.#field_ident {
if !_seen.insert(item) {
violations.push(::prost_protovalidate::Violation::new(
#proto_name, #rule_id, #msg,
));
break;
}
}
}
}
}
pub(crate) fn generate(
rules: &RepeatedRules,
field: &FieldDescriptor,
field_ident: &Ident,
proto_name: &str,
_pool: &DescriptorPool,
naming: &NamingContext,
) -> Result<Vec<TokenStream>, Error> {
let mut checks = Vec::new();
if let Some(min) = rules.min_items {
checks.push(emit_min_items_check(field_ident, proto_name, min));
}
if let Some(max) = rules.max_items {
checks.push(emit_max_items_check(field_ident, proto_name, max));
}
if rules.unique == Some(true) {
let unique_check = match field.kind() {
Kind::Float => emit_canonical_bits_unique_check(
field_ident,
proto_name,
"e!(u32),
"e!(::prost_protovalidate::types::rules_meta::float::canonical_f32_bits),
),
Kind::Double => emit_canonical_bits_unique_check(
field_ident,
proto_name,
"e!(u64),
"e!(::prost_protovalidate::types::rules_meta::float::canonical_f64_bits),
),
_ => emit_generic_unique_check(field_ident, proto_name),
};
checks.push(unique_check);
}
if let Some(ref items) = rules.items {
let items_ignore = codegen::ignore_mode_of(items.ignore);
if items_ignore != Ignore::Always {
if let Some(ref type_rules) = items.r#type {
let item_access = quote!((*_item));
let defined_values = rules::defined_enum_values(&field.kind());
let item_checks = rules::generate_scalar_type_checks(
type_rules,
&item_access,
"",
&defined_values,
naming.backend(),
)?;
if !item_checks.is_empty() {
let body = if items_ignore == Ignore::IfZeroValue {
if let Some(default_check) = codegen::generate_element_default_check(
&field.kind(),
&item_access,
naming.backend(),
) {
quote! {
if #default_check {
let violations = &mut _local_violations;
#(#item_checks)*
}
}
} else {
quote! {
let violations = &mut _local_violations;
#(#item_checks)*
}
}
} else {
quote! {
let violations = &mut _local_violations;
#(#item_checks)*
}
};
let items_prefix = meta::ITEMS_RULE_PREFIX;
checks.push(quote! {
for (_idx, _item) in self.#field_ident.iter().enumerate() {
let mut _local_violations: ::std::vec::Vec<
::prost_protovalidate::Violation,
> = ::std::vec::Vec::new();
{
#body
}
let _idx_u64: u64 = _idx as u64;
for mut _v in _local_violations {
_v.prepend_rule_path(#items_prefix);
_v.prepend_index(#proto_name, _idx_u64);
violations.push(_v);
}
}
});
}
}
}
}
Ok(checks)
}