helm_schema_core/
guard_dnf.rs1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5use crate::guard_algebra::minimize_disjunction_by;
6use crate::{ConditionalGuard, Guard, Predicate};
7
8#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct GuardDnf(BTreeSet<BTreeSet<Predicate>>);
14
15impl Default for GuardDnf {
16 fn default() -> Self {
17 Self::unconditional()
18 }
19}
20
21impl GuardDnf {
22 #[must_use]
24 pub fn unconditional() -> Self {
25 Self(BTreeSet::from([BTreeSet::new()]))
26 }
27
28 #[must_use]
30 pub fn never() -> Self {
31 Self(BTreeSet::new())
32 }
33
34 #[must_use]
36 pub fn from_conjunction(predicates: impl IntoIterator<Item = Predicate>) -> Self {
37 Self::from_disjunction([predicates])
38 }
39
40 #[must_use]
42 pub fn from_guards(guards: impl IntoIterator<Item = Guard>) -> Self {
43 Self::from_conjunction(guards.into_iter().map(Predicate::from))
44 }
45
46 #[must_use]
48 pub fn from_guard_disjunction(
49 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = Guard>>,
50 ) -> Self {
51 Self::from_disjunction(
52 conjunctions
53 .into_iter()
54 .map(|guards| guards.into_iter().map(Predicate::from)),
55 )
56 }
57
58 #[must_use]
61 pub fn from_contract_predicate_disjunction(
62 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = Predicate>>,
63 ) -> Self {
64 Self::from_guard_disjunction(conjunctions.into_iter().map(|conjunction| {
65 let predicates = conjunction.into_iter().collect::<Vec<_>>();
66 Predicate::contract_guard_stack(&predicates)
67 }))
68 }
69
70 #[must_use]
73 pub fn from_contract_predicate_disjunction_preserving_evidence(
74 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = Predicate>>,
75 ) -> Self {
76 let mut condition = Self::never();
77 for conjunction in conjunctions {
78 condition
79 .union_preserving_disjuncts(Self::from_contract_predicate_conjunction(conjunction));
80 }
81 condition
82 }
83
84 #[must_use]
86 pub fn from_contract_predicate_conjunction(
87 predicates: impl IntoIterator<Item = Predicate>,
88 ) -> Self {
89 Self::from_contract_predicate_disjunction([predicates])
90 }
91
92 #[must_use]
94 pub fn normalize_conditional_guard_disjunction(
95 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = ConditionalGuard>>,
96 ) -> Vec<Vec<ConditionalGuard>> {
97 let keys = conjunctions
98 .into_iter()
99 .map(|conjunction| {
100 let mut key = conjunction.into_iter().collect::<Vec<_>>();
101 key.sort();
102 key.dedup();
103 key
104 })
105 .collect();
106 minimize_disjunction_by(keys, crate::guard_algebra::guards_are_complementary)
107 }
108
109 #[must_use]
111 pub fn from_disjunction(
112 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = Predicate>>,
113 ) -> Self {
114 let keys = conjunctions
115 .into_iter()
116 .filter_map(normalize_conjunction)
117 .collect::<Vec<_>>();
118 let keys = minimize_disjunction_by(keys, predicates_are_complementary);
119 Self(
120 keys.into_iter()
121 .map(|key| key.into_iter().collect())
122 .collect(),
123 )
124 }
125
126 #[must_use]
128 pub fn is_unconditional(&self) -> bool {
129 self.0.contains(&BTreeSet::new())
130 }
131
132 #[must_use]
134 pub fn is_never(&self) -> bool {
135 self.0.is_empty()
136 }
137
138 #[must_use]
140 pub fn disjuncts(&self) -> &BTreeSet<BTreeSet<Predicate>> {
141 &self.0
142 }
143
144 #[must_use]
146 pub fn guard_conjunctions(&self) -> Vec<Vec<Guard>> {
147 let mut seen = BTreeSet::new();
148 let mut projected = Vec::new();
149 for conjunction in &self.0 {
150 let mut guards =
155 Predicate::contract_guard_stack(&conjunction.iter().cloned().collect::<Vec<_>>());
156 Guard::canonicalize_all(&mut guards);
157 if seen.insert(guards.clone()) {
158 projected.push(guards);
159 }
160 }
161 projected
162 }
163
164 #[must_use]
166 pub fn single_guard_conjunction(&self) -> Option<Vec<Guard>> {
167 let [guards] = self.guard_conjunctions().try_into().ok()?;
168 Some(guards)
169 }
170
171 #[must_use]
173 pub fn conjoined(&self, other: &Self) -> Self {
174 Self::from_disjunction(self.0.iter().flat_map(|left| {
175 other.0.iter().map(|right| {
176 left.iter()
177 .chain(right)
178 .cloned()
179 .collect::<Vec<Predicate>>()
180 })
181 }))
182 }
183
184 #[must_use]
186 pub fn conjoined_with_guards(&self, guards: impl IntoIterator<Item = Guard>) -> Self {
187 self.conjoined(&Self::from_guards(guards))
188 }
189
190 pub fn union_preserving_disjuncts(&mut self, other: Self) {
192 self.0.extend(other.0);
195 }
196
197 pub fn union_absorbing(&mut self, other: Self) {
201 *self = Self::from_disjunction(std::mem::take(&mut self.0).into_iter().chain(other.0));
202 }
203
204 pub fn map_value_paths<F>(&mut self, map: &mut F)
206 where
207 F: FnMut(&str) -> String,
208 {
209 *self =
210 Self::from_disjunction(std::mem::take(&mut self.0).into_iter().map(|conjunction| {
211 conjunction
212 .into_iter()
213 .map(|predicate| predicate.map_value_paths(map))
214 .collect::<Vec<_>>()
215 }));
216 }
217}
218
219impl Serialize for GuardDnf {
220 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
221 where
222 S: Serializer,
223 {
224 self.guard_conjunctions().serialize(serializer)
225 }
226}
227
228impl<'de> Deserialize<'de> for GuardDnf {
229 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
230 where
231 D: Deserializer<'de>,
232 {
233 let conjunctions = Vec::<Vec<Guard>>::deserialize(deserializer)?;
234 Ok(Self::from_guard_disjunction(conjunctions))
235 }
236}
237
238fn normalize_conjunction(
239 predicates: impl IntoIterator<Item = Predicate>,
240) -> Option<Vec<Predicate>> {
241 fn push(predicate: Predicate, normalized: &mut BTreeSet<Predicate>) -> bool {
242 match predicate {
243 Predicate::True => true,
244 Predicate::False => false,
245 Predicate::And(predicates) => predicates
246 .into_iter()
247 .all(|predicate| push(predicate, normalized)),
248 predicate => {
249 if normalized
250 .iter()
251 .any(|other| predicates_are_contradictory(&predicate, other))
252 {
253 return false;
254 }
255 normalized.insert(predicate);
256 true
257 }
258 }
259 }
260
261 let mut normalized = BTreeSet::new();
262 for predicate in predicates {
263 if !push(predicate, &mut normalized) {
264 return None;
265 }
266 }
267 Some(normalized.into_iter().collect())
268}
269
270fn predicates_are_contradictory(left: &Predicate, right: &Predicate) -> bool {
271 if predicates_are_complementary(left, right) {
272 return true;
273 }
274
275 matches!(
276 (left, right),
277 (
278 Predicate::Guard(Guard::Eq {
279 path: left_path,
280 value: left_value,
281 }),
282 Predicate::Guard(Guard::NotEq {
283 path: right_path,
284 value: right_value,
285 })
286 ) | (
287 Predicate::Guard(Guard::NotEq {
288 path: left_path,
289 value: left_value,
290 }),
291 Predicate::Guard(Guard::Eq {
292 path: right_path,
293 value: right_value,
294 })
295 ) if left_path == right_path && left_value == right_value
296 )
297}
298
299fn predicates_are_complementary(left: &Predicate, right: &Predicate) -> bool {
300 match (left, right) {
301 (predicate, Predicate::Not(negated)) | (Predicate::Not(negated), predicate) => {
302 predicate == negated.as_ref()
303 }
304 _ => false,
305 }
306}
307
308#[cfg(test)]
309#[path = "tests/guard_dnf.rs"]
310mod tests;