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}