1use std::collections::BTreeSet;
2
3use crate::{Guard, GuardValue};
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub enum ApproximationRole {
8 #[default]
10 Control,
11 OutputSelection,
14}
15
16#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum Predicate {
19 True,
21 False,
23 Approximate {
28 marker: String,
30 paths: BTreeSet<String>,
32 role: ApproximationRole,
35 sound_subset: Option<Box<Predicate>>,
40 },
41 Guard(Guard),
43 Not(Box<Predicate>),
45 And(Vec<Predicate>),
47 Or(Vec<Predicate>),
49}
50
51impl From<Guard> for Predicate {
52 fn from(guard: Guard) -> Self {
53 match guard {
54 Guard::Not { path } => Self::Not(Box::new(Self::truthy_path(path))),
55 Guard::Or { paths } => Self::Or(paths.into_iter().map(Self::truthy_path).collect()),
56 Guard::AnyOf { alternatives } => Self::Or(
57 alternatives
58 .into_iter()
59 .map(|alternative| Self::all(alternative.into_iter().map(Self::from).collect()))
60 .collect(),
61 ),
62 Guard::NotTypeIs { path, schema_type } => {
63 Self::Not(Box::new(Self::Guard(Guard::TypeIs { path, schema_type })))
64 }
65 guard => Self::Guard(guard),
66 }
67 }
68}
69
70impl Predicate {
71 pub fn truthy_path(path: impl Into<String>) -> Self {
73 Self::Guard(Guard::Truthy { path: path.into() })
74 }
75
76 pub fn invalid_kind_path(path: impl Into<String>) -> Self {
78 let path = path.into();
79 Self::Or(vec![
80 Self::from(Guard::Absent { path: path.clone() }),
81 Self::from(Guard::Eq {
82 path,
83 value: GuardValue::Null,
84 }),
85 ])
86 }
87
88 pub fn approximate(marker: impl Into<String>, paths: BTreeSet<String>) -> Self {
90 Self::Approximate {
91 marker: marker.into(),
92 paths,
93 role: ApproximationRole::Control,
94 sound_subset: None,
95 }
96 }
97
98 pub fn approximate_with_sound_subset(
102 marker: impl Into<String>,
103 paths: BTreeSet<String>,
104 sound_subset: Vec<Guard>,
105 ) -> Self {
106 let sound_subset = match sound_subset.as_slice() {
107 [] => None,
108 _ => Some(Box::new(Self::all(
109 sound_subset.into_iter().map(Self::from).collect(),
110 ))),
111 };
112 Self::Approximate {
113 marker: marker.into(),
114 paths,
115 role: ApproximationRole::Control,
116 sound_subset,
117 }
118 }
119
120 #[must_use]
122 pub fn approximate_with_sound_predicate(
123 marker: impl Into<String>,
124 paths: BTreeSet<String>,
125 sound_subset: Self,
126 ) -> Self {
127 let sound_subset = (!matches!(sound_subset, Self::False)
128 && !sound_subset.contains_approximation())
129 .then(|| Box::new(sound_subset.normalize_boolean()));
130 Self::Approximate {
131 marker: marker.into(),
132 paths,
133 role: ApproximationRole::Control,
134 sound_subset,
135 }
136 }
137
138 #[must_use]
141 pub fn approximate_output_selection(
142 marker: impl Into<String>,
143 paths: BTreeSet<String>,
144 sound_subset: Self,
145 ) -> Self {
146 let sound_subset = (!matches!(sound_subset, Self::False)
147 && !sound_subset.contains_approximation())
148 .then(|| Box::new(sound_subset.normalize_boolean()));
149 Self::Approximate {
150 marker: marker.into(),
151 paths,
152 role: ApproximationRole::OutputSelection,
153 sound_subset,
154 }
155 }
156
157 #[must_use]
159 pub fn all(predicates: Vec<Self>) -> Self {
160 match predicates.as_slice() {
161 [] => Self::True,
162 [predicate] => predicate.clone(),
163 _ => Self::And(predicates),
164 }
165 }
166
167 #[must_use]
169 pub fn negated(&self) -> Self {
170 match self {
171 Self::True => Self::False,
172 Self::False => Self::True,
173 Self::Not(inner) => inner.as_ref().clone(),
174 other => Self::Not(Box::new(other.clone())),
175 }
176 }
177
178 #[must_use]
184 pub fn normalize_boolean(self) -> Self {
185 crate::predicate_bdd::normalize(self)
186 }
187
188 #[must_use]
193 pub fn exactly_implies(&self, consequent: &Self) -> bool {
194 crate::predicate_bdd::exact_implies(self, consequent)
195 }
196
197 #[must_use]
199 pub fn is_trivial(&self) -> bool {
200 matches!(self, Self::True | Self::False)
201 }
202
203 #[must_use]
205 pub fn contains_approximation(&self) -> bool {
206 match self {
207 Self::Approximate { .. } => true,
208 Self::Not(inner) => inner.contains_approximation(),
209 Self::And(predicates) | Self::Or(predicates) => {
210 predicates.iter().any(Self::contains_approximation)
211 }
212 Self::True | Self::False | Self::Guard(_) => false,
213 }
214 }
215
216 #[must_use]
218 pub fn value_paths(&self) -> BTreeSet<String> {
219 let mut paths = BTreeSet::new();
220 self.collect_value_paths(&mut paths);
221 paths
222 }
223
224 pub fn with_context_predicates(self) -> Vec<Self> {
226 match self {
227 Self::True => Vec::new(),
228 Self::False => vec![Self::False],
229 Self::Approximate { .. }
230 | Self::Guard(
231 Guard::Range { .. }
232 | Guard::RangeKeyPrefix { .. }
233 | Guard::RangeKeyEquals { .. }
234 | Guard::RangeKeyMatches { .. }
235 | Guard::Absent { .. }
236 | Guard::With { .. }
237 | Guard::Default { .. }
238 | Guard::TypeIs { .. }
239 | Guard::NotTypeIs { .. }
240 | Guard::Not { .. }
241 | Guard::Or { .. }
242 | Guard::AnyOf { .. }
243 | Guard::IntGt { .. }
244 | Guard::IntLt { .. }
245 | Guard::AtMostOneMember { .. }
246 | Guard::MinMembers { .. }
247 | Guard::HasKey { .. }
248 | Guard::NotHasKey { .. }
249 | Guard::ContainsEquals { .. }
250 | Guard::ContainsMemberEquals { .. }
251 | Guard::ContainsTruthyMember { .. },
252 ) => vec![self],
253 Self::And(predicates) => predicates
254 .into_iter()
255 .flat_map(Self::with_context_predicates)
256 .collect(),
257 Self::Guard(Guard::Truthy { path }) => vec![Self::from(Guard::With { path })],
258 Self::Or(predicates) => {
259 let paths: Option<Vec<String>> = predicates
260 .iter()
261 .map(|predicate| match predicate {
262 Self::Guard(Guard::Truthy { path }) => Some(path.clone()),
263 _ => None,
264 })
265 .collect();
266 let Some(paths) = paths else {
267 return vec![Self::Or(predicates)];
268 };
269 let mut out: Vec<Self> = paths
270 .iter()
271 .map(|path| Self::from(Guard::With { path: path.clone() }))
272 .collect();
273 out.push(Self::Or(paths.into_iter().map(Self::truthy_path).collect()));
274 out
275 }
276 Self::Not(inner) => match inner.as_ref() {
277 Self::Guard(Guard::Truthy { path }) => vec![
278 Self::from(Guard::With { path: path.clone() }),
279 Self::Not(inner),
280 ],
281 _ => vec![Self::Not(inner)],
282 },
283 Self::Guard(Guard::Eq { path, value }) => vec![
284 Self::from(Guard::With { path: path.clone() }),
285 Self::from(Guard::Eq { path, value }),
286 ],
287 Self::Guard(Guard::MatchesPattern {
288 path,
289 pattern,
290 templated,
291 }) => vec![
292 Self::from(Guard::With { path: path.clone() }),
293 Self::from(Guard::MatchesPattern {
294 path,
295 pattern,
296 templated,
297 }),
298 ],
299 Self::Guard(Guard::NotMatchesPattern { path, pattern }) => vec![
300 Self::from(Guard::With { path: path.clone() }),
301 Self::from(Guard::NotMatchesPattern { path, pattern }),
302 ],
303 Self::Guard(Guard::NotEq { path, value }) => vec![
304 Self::from(Guard::With { path: path.clone() }),
305 Self::from(Guard::NotEq { path, value }),
306 ],
307 }
308 }
309
310 #[must_use]
312 pub fn conditionally_optional_paths(&self) -> BTreeSet<String> {
313 let mut paths = BTreeSet::new();
314 self.collect_conditionally_optional_paths(&mut paths);
315 paths
316 }
317
318 pub fn contract_guards(&self) -> Vec<Guard> {
320 match self {
321 Self::True | Self::False | Self::Approximate { .. } => Vec::new(),
322 Self::Guard(guard) => vec![guard.clone()],
323 Self::Not(inner) => negated_contract_guards(inner),
324 Self::And(predicates) => predicates.iter().flat_map(Self::contract_guards).collect(),
325 Self::Or(predicates) => or_contract_guards(predicates),
326 }
327 }
328
329 #[must_use]
338 pub fn contract_guards_are_exact(&self) -> bool {
339 match self {
340 Self::True | Self::Guard(_) => true,
341 Self::False | Self::Approximate { .. } => false,
342 Self::Not(inner) => negation_flattens_exactly(inner),
343 Self::And(predicates) | Self::Or(predicates) => {
344 predicates.iter().all(Self::contract_guards_are_exact)
345 }
346 }
347 }
348
349 fn collect_value_paths(&self, out: &mut BTreeSet<String>) {
350 match self {
351 Self::True | Self::False => {}
352 Self::Approximate { paths, .. } => out.extend(paths.iter().cloned()),
353 Self::Guard(guard) => {
354 for path in guard.value_paths() {
355 out.insert(path.to_string());
356 }
357 }
358 Self::Not(inner) => inner.collect_value_paths(out),
359 Self::And(predicates) | Self::Or(predicates) => {
360 for predicate in predicates {
361 predicate.collect_value_paths(out);
362 }
363 }
364 }
365 }
366
367 fn collect_conditionally_optional_paths(&self, out: &mut BTreeSet<String>) {
368 match self {
369 Self::Guard(Guard::NotEq { path, .. } | Guard::Absent { path }) => {
370 out.insert(path.clone());
371 }
372 Self::Not(inner) => match inner.as_ref() {
373 Self::Guard(Guard::Truthy { path }) => {
374 out.insert(path.clone());
375 }
376 _ => inner.collect_conditionally_optional_paths(out),
377 },
378 Self::Or(predicates) => {
379 for predicate in predicates {
380 out.extend(predicate.value_paths());
381 }
382 }
383 Self::And(predicates) => {
384 for predicate in predicates {
385 predicate.collect_conditionally_optional_paths(out);
386 }
387 }
388 Self::True
389 | Self::False
390 | Self::Approximate { .. }
391 | Self::Guard(
392 Guard::Truthy { .. }
393 | Guard::Eq { .. }
394 | Guard::MatchesPattern { .. }
395 | Guard::NotMatchesPattern { .. }
396 | Guard::RangeKeyPrefix { .. }
397 | Guard::RangeKeyEquals { .. }
398 | Guard::RangeKeyMatches { .. }
399 | Guard::Range { .. }
400 | Guard::With { .. }
401 | Guard::Default { .. }
402 | Guard::TypeIs { .. }
403 | Guard::NotTypeIs { .. }
404 | Guard::Not { .. }
405 | Guard::Or { .. }
406 | Guard::AnyOf { .. }
407 | Guard::IntGt { .. }
408 | Guard::IntLt { .. }
409 | Guard::AtMostOneMember { .. }
410 | Guard::MinMembers { .. }
411 | Guard::HasKey { .. }
412 | Guard::NotHasKey { .. }
413 | Guard::ContainsEquals { .. }
414 | Guard::ContainsMemberEquals { .. }
415 | Guard::ContainsTruthyMember { .. },
416 ) => {}
417 }
418 }
419
420 #[must_use]
422 pub fn contract_guard_stack(predicates: &[Self]) -> Vec<Guard> {
423 let mut guards = Vec::new();
424 for predicate in predicates {
425 for guard in predicate.contract_guards() {
426 if !guards.contains(&guard) {
427 guards.push(guard);
428 }
429 }
430 }
431 guards
432 }
433
434 #[must_use]
436 pub fn map_value_paths<F>(self, map: &mut F) -> Self
437 where
438 F: FnMut(&str) -> String,
439 {
440 match self {
441 Self::True => Self::True,
442 Self::False => Self::False,
443 Self::Approximate {
444 marker,
445 paths,
446 role,
447 sound_subset,
448 } => Self::Approximate {
449 marker,
450 paths: paths.into_iter().map(|path| map(&path)).collect(),
451 role,
452 sound_subset: sound_subset
453 .map(|predicate| Box::new(predicate.map_value_paths(map))),
454 },
455 Self::Guard(guard) => Self::Guard(guard.map_value_paths(map)),
456 Self::Not(inner) => Self::Not(Box::new(inner.map_value_paths(map))),
457 Self::And(predicates) => Self::And(
458 predicates
459 .into_iter()
460 .map(|predicate| predicate.map_value_paths(map))
461 .collect(),
462 ),
463 Self::Or(predicates) => Self::Or(
464 predicates
465 .into_iter()
466 .map(|predicate| predicate.map_value_paths(map))
467 .collect(),
468 ),
469 }
470 }
471}
472
473fn negation_flattens_exactly(inner: &Predicate) -> bool {
477 match inner {
478 Predicate::Guard(
479 Guard::Truthy { .. }
480 | Guard::With { .. }
481 | Guard::Not { .. }
482 | Guard::Or { .. }
483 | Guard::Eq { .. }
484 | Guard::NotEq { .. }
485 | Guard::TypeIs { .. }
486 | Guard::NotTypeIs { .. }
487 | Guard::HasKey { .. }
488 | Guard::NotHasKey { .. },
489 ) => true,
490 Predicate::Not(inner) => inner.contract_guards_are_exact(),
491 Predicate::And(predicates) | Predicate::Or(predicates) => {
492 predicates.iter().all(negation_flattens_exactly)
493 }
494 _ => false,
495 }
496}
497
498fn negated_contract_guards(inner: &Predicate) -> Vec<Guard> {
499 match inner {
500 Predicate::Guard(Guard::Truthy { path } | Guard::With { path }) => {
501 vec![Guard::Not { path: path.clone() }]
502 }
503 Predicate::Guard(Guard::Not { path }) => vec![Guard::Truthy { path: path.clone() }],
504 Predicate::Guard(Guard::Or { paths }) => paths
505 .iter()
506 .map(|path| Guard::Not { path: path.clone() })
507 .collect(),
508 Predicate::Guard(Guard::Eq { path, value }) => vec![Guard::NotEq {
509 path: path.clone(),
510 value: value.clone(),
511 }],
512 Predicate::Guard(Guard::NotEq { path, value }) => vec![Guard::Eq {
513 path: path.clone(),
514 value: value.clone(),
515 }],
516 Predicate::Guard(Guard::TypeIs { path, schema_type }) => vec![Guard::NotTypeIs {
517 path: path.clone(),
518 schema_type: schema_type.clone(),
519 }],
520 Predicate::Guard(Guard::NotTypeIs { path, schema_type }) => vec![Guard::TypeIs {
521 path: path.clone(),
522 schema_type: schema_type.clone(),
523 }],
524 Predicate::Guard(Guard::HasKey { path, key }) => vec![Guard::NotHasKey {
525 path: path.clone(),
526 key: key.clone(),
527 }],
528 Predicate::Guard(Guard::NotHasKey { path, key }) => vec![Guard::HasKey {
529 path: path.clone(),
530 key: key.clone(),
531 }],
532 Predicate::Not(inner) => inner.contract_guards(),
533 Predicate::Or(predicates) => {
537 let mut guards = Vec::new();
538 for predicate in predicates {
539 let negated = negated_contract_guards(predicate);
540 if negated.is_empty() {
541 return Vec::new();
542 }
543 guards.extend(negated);
544 }
545 guards
546 }
547 Predicate::And(predicates) => {
550 let mut alternatives = Vec::new();
551 for predicate in predicates {
552 let negated = negated_contract_guards(predicate);
553 if negated.is_empty() {
554 return Vec::new();
555 }
556 alternatives.push(negated);
557 }
558 alternatives_to_guards(alternatives)
559 }
560 _ => Vec::new(),
561 }
562}
563
564fn or_contract_guards(predicates: &[Predicate]) -> Vec<Guard> {
565 let alternatives = predicates
566 .iter()
567 .map(Predicate::contract_guards)
568 .collect::<Vec<_>>();
569
570 if alternatives.iter().any(Vec::is_empty) {
571 return Vec::new();
572 }
573 alternatives_to_guards(alternatives)
574}
575
576fn alternatives_to_guards(mut alternatives: Vec<Vec<Guard>>) -> Vec<Guard> {
580 for alternative in &mut alternatives {
581 alternative.sort();
582 alternative.dedup();
583 }
584 alternatives.sort();
585 alternatives.dedup();
586
587 if alternatives.len() == 1 {
588 return alternatives.pop().unwrap_or_default();
589 }
590
591 if let Some(paths) = truthy_or_paths(&alternatives) {
592 return vec![Guard::Or { paths }];
593 }
594
595 vec![Guard::AnyOf { alternatives }]
596}
597
598fn truthy_or_paths(alternatives: &[Vec<Guard>]) -> Option<Vec<String>> {
599 alternatives
600 .iter()
601 .map(|alternative| match alternative.as_slice() {
602 [Guard::Truthy { path }] => Some(path.clone()),
603 _ => None,
604 })
605 .collect()
606}
607
608#[cfg(test)]
609#[path = "tests/predicate.rs"]
610mod tests;