Skip to main content

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 `self.threshold` subconditions are satisfied.
24    ///
25    /// A threshold of zero is rejected as malformed. A threshold exceeding the
26    /// number of subconditions is likewise rejected, since it can never be met.
27    ///
28    /// # Errors
29    ///
30    /// - [`Error::ZeroThreshold`] if threshold == 0.
31    /// - [`Error::ThresholdUnsatisfiable`] if threshold > subconditions.len().
32    /// - [`Error::ThresholdNotMet`] if fewer than threshold subconditions verify.
33    pub fn verify(&self) -> Result<(), Error> {
34        match self.threshold {
35            0 => Err(Error::ZeroThreshold),
36            required if required > self.subconditions.len() => Err(Error::ThresholdUnsatisfiable {
37                required,
38                available: self.subconditions.len(),
39            }),
40            required => {
41                let satisfied = self.count_satisfied();
42                (satisfied >= required)
43                    .then_some(())
44                    .ok_or(Error::ThresholdNotMet {
45                        required,
46                        satisfied,
47                    })
48            }
49        }
50    }
51
52    /// Counts the number of subconditions that verify successfully. A
53    /// subcondition that fails to verify simply does not count toward the
54    /// threshold.
55    fn count_satisfied(&self) -> usize {
56        self.subconditions
57            .iter()
58            .filter_map(|c| c.verify().ok())
59            .count()
60    }
61}
62
63/// Threshold conditions verification errors.
64#[derive(Debug, thiserror::Error)]
65pub enum Error {
66    /// The threshold was zero.
67    #[error("threshold must be greater than zero")]
68    ZeroThreshold,
69
70    /// The threshold exceeds the number of subconditions and can never be met.
71    #[error("threshold {required} exceeds the {available} subconditions available")]
72    ThresholdUnsatisfiable {
73        /// Minimum number of valid subconditions required.
74        required: usize,
75        /// Number of subconditions present.
76        available: usize,
77    },
78
79    /// Fewer than the required number of subconditions were satisfied.
80    #[error("needed at least {required} passes, but only {satisfied} succeeded")]
81    ThresholdNotMet {
82        /// Minimum number of valid subconditions required.
83        required: usize,
84        /// Number of verified subconditions.
85        satisfied: usize,
86    },
87}