zescrow_core/condition/
threshold.rs

1use bincode::{Decode, Encode};
2#[cfg(feature = "json")]
3use serde::{Deserialize, Serialize};
4
5use super::Condition;
6
7/// N-of-M threshold condition.
8///
9/// Satisfied when at least `threshold` of the `subconditions` verify
10/// successfully. Subconditions can be any [`Condition`] variant,
11/// including nested thresholds.
12#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
13#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
14pub struct Threshold {
15    /// Minimum number of valid subconditions required.
16    pub threshold: usize,
17
18    /// Subconditions to evaluate.
19    pub subconditions: Vec<Condition>,
20}
21
22impl Threshold {
23    /// Verifies that at least `threshold` subconditions are satisfied.
24    ///
25    /// Returns `Ok(())` if the threshold is met, or `Err` with details
26    /// about how many conditions passed versus required.
27    ///
28    /// A threshold of zero is always satisfied, regardless of subconditions.
29    pub fn verify(&self) -> Result<(), Error> {
30        (self.threshold == 0)
31            .then_some(())
32            .map(Ok)
33            .unwrap_or_else(|| self.verify_threshold())
34    }
35
36    /// Counts satisfied subconditions and checks against threshold.
37    fn verify_threshold(&self) -> Result<(), Error> {
38        let satisfied = self.count_satisfied();
39
40        (satisfied >= self.threshold)
41            .then_some(())
42            .ok_or(Error::ThresholdNotMet {
43                required: self.threshold,
44                satisfied,
45            })
46    }
47
48    /// Counts the number of subconditions that verify successfully.
49    fn count_satisfied(&self) -> usize {
50        self.subconditions
51            .iter()
52            .filter_map(|c| c.verify().ok())
53            .count()
54    }
55}
56
57/// Threshold conditions verification errors.
58#[derive(Debug, thiserror::Error)]
59pub enum Error {
60    /// Fewer than the required number of subconditions were satisfied.
61    #[error("needed at least {required} passes, but only {satisfied} succeeded")]
62    ThresholdNotMet {
63        /// Minimum number of valid subconditions required.
64        required: usize,
65        /// Number of verified subconditions.
66        satisfied: usize,
67    },
68}