prost-protovalidate 0.5.0

Runtime validation for Protocol Buffer messages using buf.validate rules
Documentation
use std::collections::HashSet;

use prost_protovalidate_types::rules_meta::enumeration as meta;

use crate::config::ValidationConfig;
use crate::error::{Error, ValidationError};
use crate::violation::Violation;

pub(crate) struct EnumRuleEval {
    r#const: Option<i32>,
    r#in: HashSet<i32>,
    not_in: HashSet<i32>,
}

impl EnumRuleEval {
    pub fn new(rules: &prost_protovalidate_types::EnumRules) -> Self {
        Self {
            r#const: rules.r#const,
            r#in: rules.r#in.iter().copied().collect(),
            not_in: rules.not_in.iter().copied().collect(),
        }
    }

    pub fn tautology(&self) -> bool {
        self.r#const.is_none() && self.r#in.is_empty() && self.not_in.is_empty()
    }

    pub fn evaluate(
        &self,
        val: &prost_reflect::Value,
        _cfg: &ValidationConfig,
    ) -> Result<(), Error> {
        let Some(v) = val.as_enum_number() else {
            return Ok(());
        };

        let mut violations = Vec::new();

        if let Some(c) = self.r#const {
            if v != c {
                violations.push(Violation::new("", meta::CONST_ID, meta::const_message(c)));
            }
        }

        if !self.r#in.is_empty() && !self.r#in.contains(&v) {
            let values: Vec<i32> = self.r#in.iter().copied().collect();
            violations.push(Violation::new("", meta::IN_ID, meta::in_message(&values)));
        }

        if self.not_in.contains(&v) {
            let values: Vec<i32> = self.not_in.iter().copied().collect();
            violations.push(Violation::new(
                "",
                meta::NOT_IN_ID,
                meta::not_in_message(&values),
            ));
        }

        if violations.is_empty() {
            Ok(())
        } else {
            Err(ValidationError::new(violations).into())
        }
    }
}