prost-protovalidate 0.3.0

Runtime validation for Protocol Buffer messages using buf.validate rules
Documentation
use prost_reflect::DynamicMessage;

use crate::config::ValidationConfig;
use crate::error::{self, Error};

use super::Evaluator;
use super::prepend_rule_prefix;
use super::value::ValueEval;

/// Evaluator for repeated (list) fields.
/// Iterates items and applies per-item rules.
pub(crate) struct ListEval {
    /// Per-item evaluator.
    pub item_rules: ValueEval,
}

impl Evaluator for ListEval {
    fn tautology(&self) -> bool {
        self.item_rules.tautology()
    }

    fn evaluate(
        &self,
        msg: &DynamicMessage,
        val: &prost_reflect::Value,
        cfg: &ValidationConfig,
    ) -> Result<(), Error> {
        let Some(list) = val.as_list() else {
            return Ok(());
        };

        let mut acc: Option<Error> = None;

        for (i, item) in list.iter().enumerate() {
            let item_path = format!("[{i}]");
            let (direct, nested) = self
                .item_rules
                .evaluate_value_split(msg, item, cfg, &item_path);
            // Only direct (item-level) violations get the "repeated.items" rule prefix.
            // Nested (embedded message) violations keep their own rule paths.
            let direct = prepend_rule_prefix(direct, "repeated.items");
            let (cont, new_acc) = error::merge_violations(acc, direct, cfg.fail_fast);
            acc = new_acc;
            if !cont {
                break;
            }
            let (cont, new_acc) = error::merge_violations(acc, nested, cfg.fail_fast);
            acc = new_acc;
            if !cont {
                break;
            }
        }

        match acc {
            Some(err) => Err(err),
            None => Ok(()),
        }
    }
}