1use 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]
60 pub fn normalize_conditional_guard_disjunction(
61 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = ConditionalGuard>>,
62 ) -> Vec<Vec<ConditionalGuard>> {
63 let keys = conjunctions
64 .into_iter()
65 .map(|conjunction| {
66 let mut key = conjunction.into_iter().collect::<Vec<_>>();
67 key.sort();
68 key.dedup();
69 key
70 })
71 .collect();
72 minimize_disjunction_by(keys, crate::guard_algebra::guards_are_complementary)
73 }
74
75 #[must_use]
77 pub fn from_disjunction(
78 conjunctions: impl IntoIterator<Item = impl IntoIterator<Item = Predicate>>,
79 ) -> Self {
80 let keys = conjunctions
81 .into_iter()
82 .filter_map(normalize_conjunction)
83 .collect::<Vec<_>>();
84 let keys = minimize_disjunction_by(keys, predicates_are_complementary);
85 Self(
86 keys.into_iter()
87 .map(|key| key.into_iter().collect())
88 .collect(),
89 )
90 }
91
92 #[must_use]
94 pub fn is_unconditional(&self) -> bool {
95 self.0.contains(&BTreeSet::new())
96 }
97
98 #[must_use]
100 pub fn is_never(&self) -> bool {
101 self.0.is_empty()
102 }
103
104 #[must_use]
106 pub fn disjuncts(&self) -> &BTreeSet<BTreeSet<Predicate>> {
107 &self.0
108 }
109
110 #[must_use]
112 pub fn guard_conjunctions(&self) -> Vec<Vec<Guard>> {
113 let mut seen = BTreeSet::new();
114 let mut projected = Vec::new();
115 for conjunction in &self.0 {
116 let mut guards =
121 Predicate::contract_guard_stack(&conjunction.iter().cloned().collect::<Vec<_>>());
122 Guard::canonicalize_all(&mut guards);
123 if seen.insert(guards.clone()) {
124 projected.push(guards);
125 }
126 }
127 projected
128 }
129
130 #[must_use]
132 pub fn single_guard_conjunction(&self) -> Option<Vec<Guard>> {
133 let [guards] = self.guard_conjunctions().try_into().ok()?;
134 Some(guards)
135 }
136
137 #[must_use]
139 pub fn conjoined(&self, other: &Self) -> Self {
140 Self::from_disjunction(self.0.iter().flat_map(|left| {
141 other.0.iter().map(|right| {
142 left.iter()
143 .chain(right)
144 .cloned()
145 .collect::<Vec<Predicate>>()
146 })
147 }))
148 }
149
150 #[must_use]
152 pub fn conjoined_with_guards(&self, guards: impl IntoIterator<Item = Guard>) -> Self {
153 self.conjoined(&Self::from_guards(guards))
154 }
155
156 pub fn union_absorbing(&mut self, other: Self) {
159 *self = Self::from_disjunction(std::mem::take(&mut self.0).into_iter().chain(other.0));
160 }
161
162 pub fn map_value_paths<F>(&mut self, map: &mut F)
164 where
165 F: FnMut(&str) -> String,
166 {
167 *self =
168 Self::from_disjunction(std::mem::take(&mut self.0).into_iter().map(|conjunction| {
169 conjunction
170 .into_iter()
171 .map(|predicate| predicate.map_value_paths(map))
172 .collect::<Vec<_>>()
173 }));
174 }
175}
176
177impl Serialize for GuardDnf {
178 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179 where
180 S: Serializer,
181 {
182 self.guard_conjunctions().serialize(serializer)
183 }
184}
185
186impl<'de> Deserialize<'de> for GuardDnf {
187 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
188 where
189 D: Deserializer<'de>,
190 {
191 let conjunctions = Vec::<Vec<Guard>>::deserialize(deserializer)?;
192 Ok(Self::from_guard_disjunction(conjunctions))
193 }
194}
195
196fn normalize_conjunction(
197 predicates: impl IntoIterator<Item = Predicate>,
198) -> Option<Vec<Predicate>> {
199 fn push(predicate: Predicate, normalized: &mut BTreeSet<Predicate>) -> bool {
200 match predicate {
201 Predicate::True => true,
202 Predicate::False => false,
203 Predicate::And(predicates) => predicates
204 .into_iter()
205 .all(|predicate| push(predicate, normalized)),
206 Predicate::Or(predicates) if disjunction_is_tautology(&predicates) => true,
207 predicate => {
208 if normalized
209 .iter()
210 .any(|other| predicates_are_contradictory(&predicate, other))
211 {
212 return false;
213 }
214 normalized.insert(predicate);
215 true
216 }
217 }
218 }
219
220 let mut normalized = BTreeSet::new();
221 for predicate in predicates {
222 if !push(predicate, &mut normalized) {
223 return None;
224 }
225 }
226 let absorbed_disjunctions = normalized
227 .iter()
228 .filter(|predicate| match predicate {
229 Predicate::Or(alternatives) => alternatives
230 .iter()
231 .any(|alternative| normalized.contains(alternative)),
232 _ => false,
233 })
234 .cloned()
235 .collect::<Vec<_>>();
236 for predicate in absorbed_disjunctions {
237 normalized.remove(&predicate);
238 }
239 let exact = Predicate::all(
240 normalized
241 .iter()
242 .filter(|predicate| !predicate.contains_approximation())
243 .cloned()
244 .collect(),
245 );
246 normalized.retain(|predicate| {
247 let Predicate::Approximate {
248 sound_subset: Some(sound_subset),
249 ..
250 } = predicate
251 else {
252 return true;
253 };
254 !crate::predicate_bdd::exact_implies(&exact, sound_subset)
255 });
256 Some(normalized.into_iter().collect())
257}
258
259fn disjunction_is_tautology(predicates: &[Predicate]) -> bool {
260 predicates.iter().any(|predicate| {
261 predicates
262 .iter()
263 .any(|other| predicates_are_complementary(predicate, other))
264 })
265}
266
267fn predicates_are_contradictory(left: &Predicate, right: &Predicate) -> bool {
268 if predicates_are_complementary(left, right) {
269 return true;
270 }
271
272 if matches!(
273 (left, right),
274 (
275 Predicate::Guard(Guard::Eq {
276 path: left_path,
277 value: left_value,
278 }),
279 Predicate::Guard(Guard::NotEq {
280 path: right_path,
281 value: right_value,
282 })
283 ) | (
284 Predicate::Guard(Guard::NotEq {
285 path: left_path,
286 value: left_value,
287 }),
288 Predicate::Guard(Guard::Eq {
289 path: right_path,
290 value: right_value,
291 })
292 ) if left_path == right_path && left_value == right_value
293 ) {
294 return true;
295 }
296
297 match (left, right) {
298 (
299 Predicate::Guard(Guard::Eq {
300 path: left_path,
301 value: left_value,
302 }),
303 Predicate::Guard(Guard::Eq {
304 path: right_path,
305 value: right_value,
306 }),
307 ) => left_path == right_path && left_value != right_value,
308 (
309 Predicate::Guard(Guard::MatchesPattern {
310 path: pattern_path, ..
311 }),
312 Predicate::Guard(Guard::Eq {
313 path: value_path,
314 value,
315 }),
316 )
317 | (
318 Predicate::Guard(Guard::Eq {
319 path: value_path,
320 value,
321 }),
322 Predicate::Guard(Guard::MatchesPattern {
323 path: pattern_path, ..
324 }),
325 ) => pattern_path == value_path && !matches!(value, crate::GuardValue::String(_)),
326 (
327 Predicate::Guard(Guard::TypeIs {
328 path: type_path,
329 schema_type,
330 }),
331 Predicate::Guard(Guard::Eq {
332 path: value_path,
333 value,
334 }),
335 )
336 | (
337 Predicate::Guard(Guard::Eq {
338 path: value_path,
339 value,
340 }),
341 Predicate::Guard(Guard::TypeIs {
342 path: type_path,
343 schema_type,
344 }),
345 ) => type_path == value_path && !guard_value_has_schema_type(value, schema_type),
346 _ => false,
347 }
348}
349
350fn guard_value_has_schema_type(value: &crate::GuardValue, schema_type: &str) -> bool {
351 match value {
352 crate::GuardValue::String(_) => schema_type == "string",
353 crate::GuardValue::Bool(_) => schema_type == "boolean",
354 crate::GuardValue::Int(_) => matches!(schema_type, "integer" | "number"),
355 crate::GuardValue::Float(_) => schema_type == "number",
356 crate::GuardValue::Null => schema_type == "null",
357 }
358}
359
360fn predicates_are_complementary(left: &Predicate, right: &Predicate) -> bool {
361 match (left, right) {
362 (predicate, Predicate::Not(negated)) | (Predicate::Not(negated), predicate) => {
363 predicate == negated.as_ref()
364 }
365 _ => false,
366 }
367}
368
369#[cfg(test)]
370#[path = "tests/guard_dnf.rs"]
371mod tests;