mod builder;
pub use builder::AnyValidatorBuilder;
use proto_types::Any;
use super::*;
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnyValidator {
pub cel: Vec<CelProgram>,
pub ignore: Ignore,
pub required: bool,
pub in_: Option<SortedList<FixedStr>>,
pub not_in: Option<SortedList<FixedStr>>,
pub error_messages: Option<ErrorMessages<AnyViolation>>,
}
impl Validator<Any> for AnyValidator {
type Target = Any;
impl_testing_methods!();
#[inline(never)]
#[cold]
fn check_consistency(&self) -> Result<(), Vec<ConsistencyError>> {
let mut errors = Vec::new();
#[cfg(feature = "cel")]
if let Err(e) = self.__check_cel_programs() {
errors.extend(e.into_iter().map(ConsistencyError::from));
}
if let Some(custom_messages) = self.error_messages.as_deref() {
let mut unused_messages: Vec<String> = Vec::new();
for key in custom_messages.keys() {
let is_used = match key {
AnyViolation::Required => self.required,
AnyViolation::In => self.in_.is_some(),
AnyViolation::NotIn => self.not_in.is_some(),
_ => true,
};
if !is_used {
unused_messages.push(format!("{key:?}"));
}
}
if !unused_messages.is_empty() {
errors.push(ConsistencyError::UnusedCustomMessages(unused_messages));
}
}
if let Err(e) = check_list_rules(self.in_.as_ref(), self.not_in.as_ref()) {
errors.push(e.into());
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn execute_validation(
&self,
ctx: &mut ValidationCtx,
val: Option<&Self::Target>,
) -> ValidationResult {
handle_ignore_always!(&self.ignore);
handle_ignore_if_zero_value!(&self.ignore, val.is_none_or(|v| v.is_default()));
let mut is_valid = IsValid::Yes;
macro_rules! handle_violation {
($id:ident, $default:expr) => {
is_valid &= ctx.add_violation(
ViolationKind::Any(AnyViolation::$id),
self.error_messages
.as_deref()
.and_then(|map| map.get(&AnyViolation::$id))
.map(|m| Cow::Borrowed(m.as_ref()))
.unwrap_or_else(|| Cow::Owned($default)),
)?;
};
}
if let Some(val) = val {
if let Some(allowed_list) = &self.in_
&& !allowed_list.contains(val.type_url.as_str())
{
handle_violation!(
In,
format!(
"must have one of these type URLs: {}",
FixedStr::__format_list(allowed_list)
)
);
}
if let Some(forbidden_list) = &self.not_in
&& forbidden_list.contains(val.type_url.as_str())
{
handle_violation!(
NotIn,
format!(
"cannot have one of these type URLs: {}",
FixedStr::__format_list(forbidden_list)
)
);
}
#[cfg(feature = "cel")]
if !self.cel.is_empty() {
let cel_ctx = ProgramsExecutionCtx {
programs: &self.cel,
value: val.clone(),
ctx,
};
is_valid &= cel_ctx.execute_programs()?;
}
} else if self.required {
handle_violation!(Required, "is required".to_string());
}
Ok(is_valid)
}
#[inline(never)]
#[cold]
fn schema(&self) -> Option<ValidatorSchema> {
Some(ValidatorSchema {
schema: self.clone().into(),
cel_rules: self.__cel_rules(),
imports: vec!["buf/validate/validate.proto".into()],
})
}
}
impl From<AnyValidator> for ProtoOption {
#[inline(never)]
#[cold]
fn from(validator: AnyValidator) -> Self {
let mut rules = OptionMessageBuilder::new();
rules
.maybe_set(
"in",
validator
.in_
.map(|list| OptionValue::new_list(list)),
)
.maybe_set(
"not_in",
validator
.not_in
.map(|list| OptionValue::new_list(list)),
);
let mut outer_rules = OptionMessageBuilder::new();
if !rules.is_empty() {
outer_rules.set("any", OptionValue::Message(rules.build()));
}
outer_rules
.add_cel_options(validator.cel)
.set_required(validator.required)
.set_ignore(validator.ignore);
Self {
name: "(buf.validate.field)".into(),
value: OptionValue::Message(outer_rules.build()),
}
}
}