1use std::collections::BTreeSet;
2
3use crate::Guard;
4
5#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub enum Predicate {
8 True,
10 False,
12 Approximate {
17 marker: String,
19 paths: BTreeSet<String>,
21 sound_subset: Vec<Guard>,
27 },
28 Guard(Guard),
30 Not(Box<Predicate>),
32 And(Vec<Predicate>),
34 Or(Vec<Predicate>),
36}
37
38impl From<Guard> for Predicate {
39 fn from(guard: Guard) -> Self {
40 match guard {
41 Guard::Not { path } => Self::Not(Box::new(Self::truthy_path(path))),
42 Guard::Or { paths } => Self::Or(paths.into_iter().map(Self::truthy_path).collect()),
43 Guard::AnyOf { alternatives } => Self::Or(
44 alternatives
45 .into_iter()
46 .map(|alternative| Self::all(alternative.into_iter().map(Self::from).collect()))
47 .collect(),
48 ),
49 Guard::NotTypeIs { path, schema_type } => {
50 Self::Not(Box::new(Self::Guard(Guard::TypeIs { path, schema_type })))
51 }
52 guard => Self::Guard(guard),
53 }
54 }
55}
56
57impl Predicate {
58 pub fn truthy_path(path: impl Into<String>) -> Self {
60 Self::Guard(Guard::Truthy { path: path.into() })
61 }
62
63 pub fn approximate(marker: impl Into<String>, paths: BTreeSet<String>) -> Self {
65 Self::Approximate {
66 marker: marker.into(),
67 paths,
68 sound_subset: Vec::new(),
69 }
70 }
71
72 pub fn approximate_with_sound_subset(
76 marker: impl Into<String>,
77 paths: BTreeSet<String>,
78 sound_subset: Vec<Guard>,
79 ) -> Self {
80 Self::Approximate {
81 marker: marker.into(),
82 paths,
83 sound_subset,
84 }
85 }
86
87 #[must_use]
89 pub fn all(predicates: Vec<Self>) -> Self {
90 match predicates.as_slice() {
91 [] => Self::True,
92 [predicate] => predicate.clone(),
93 _ => Self::And(predicates),
94 }
95 }
96
97 #[must_use]
99 pub fn negated(&self) -> Self {
100 match self {
101 Self::True => Self::False,
102 Self::False => Self::True,
103 Self::Not(inner) => inner.as_ref().clone(),
104 other => Self::Not(Box::new(other.clone())),
105 }
106 }
107
108 #[must_use]
110 pub fn is_trivial(&self) -> bool {
111 matches!(self, Self::True | Self::False)
112 }
113
114 #[must_use]
117 pub fn contains_approximation(&self) -> bool {
118 match self {
119 Self::Approximate { .. } => true,
120 Self::Not(inner) => inner.contains_approximation(),
121 Self::And(predicates) | Self::Or(predicates) => {
122 predicates.iter().any(Self::contains_approximation)
123 }
124 Self::True | Self::False | Self::Guard(_) => false,
125 }
126 }
127
128 #[must_use]
130 pub fn value_paths(&self) -> BTreeSet<String> {
131 let mut paths = BTreeSet::new();
132 self.collect_value_paths(&mut paths);
133 paths
134 }
135
136 pub fn with_context_predicates(self) -> Vec<Self> {
138 match self {
139 Self::True => Vec::new(),
140 Self::False => vec![Self::False],
141 Self::Approximate { .. }
142 | Self::Guard(
143 Guard::Range { .. }
144 | Guard::RangeKeyPrefix { .. }
145 | Guard::RangeKeyEquals { .. }
146 | Guard::RangeKeyMatches { .. }
147 | Guard::Absent { .. }
148 | Guard::With { .. }
149 | Guard::Default { .. }
150 | Guard::TypeIs { .. }
151 | Guard::NotTypeIs { .. }
152 | Guard::Not { .. }
153 | Guard::Or { .. }
154 | Guard::AnyOf { .. }
155 | Guard::IntGt { .. }
156 | Guard::IntLt { .. }
157 | Guard::AtMostOneMember { .. }
158 | Guard::MinMembers { .. }
159 | Guard::HasKey { .. }
160 | Guard::ContainsEquals { .. },
161 ) => vec![self],
162 Self::And(predicates) => predicates
163 .into_iter()
164 .flat_map(Self::with_context_predicates)
165 .collect(),
166 Self::Guard(Guard::Truthy { path }) => vec![Self::from(Guard::With { path })],
167 Self::Or(predicates) => {
168 let paths: Option<Vec<String>> = predicates
169 .iter()
170 .map(|predicate| match predicate {
171 Self::Guard(Guard::Truthy { path }) => Some(path.clone()),
172 _ => None,
173 })
174 .collect();
175 let Some(paths) = paths else {
176 return vec![Self::Or(predicates)];
177 };
178 let mut out: Vec<Self> = paths
179 .iter()
180 .map(|path| Self::from(Guard::With { path: path.clone() }))
181 .collect();
182 out.push(Self::Or(paths.into_iter().map(Self::truthy_path).collect()));
183 out
184 }
185 Self::Not(inner) => match inner.as_ref() {
186 Self::Guard(Guard::Truthy { path }) => vec![
187 Self::from(Guard::With { path: path.clone() }),
188 Self::Not(inner),
189 ],
190 _ => vec![Self::Not(inner)],
191 },
192 Self::Guard(Guard::Eq { path, value }) => vec![
193 Self::from(Guard::With { path: path.clone() }),
194 Self::from(Guard::Eq { path, value }),
195 ],
196 Self::Guard(Guard::MatchesPattern {
197 path,
198 pattern,
199 templated,
200 }) => vec![
201 Self::from(Guard::With { path: path.clone() }),
202 Self::from(Guard::MatchesPattern {
203 path,
204 pattern,
205 templated,
206 }),
207 ],
208 Self::Guard(Guard::NotEq { path, value }) => vec![
209 Self::from(Guard::With { path: path.clone() }),
210 Self::from(Guard::NotEq { path, value }),
211 ],
212 }
213 }
214
215 #[must_use]
217 pub fn conditionally_optional_paths(&self) -> BTreeSet<String> {
218 let mut paths = BTreeSet::new();
219 self.collect_conditionally_optional_paths(&mut paths);
220 paths
221 }
222
223 pub fn contract_guards(&self) -> Vec<Guard> {
225 match self {
226 Self::True | Self::False | Self::Approximate { .. } => Vec::new(),
227 Self::Guard(guard) => vec![guard.clone()],
228 Self::Not(inner) => negated_contract_guards(inner),
229 Self::And(predicates) => predicates.iter().flat_map(Self::contract_guards).collect(),
230 Self::Or(predicates) => or_contract_guards(predicates),
231 }
232 }
233
234 #[must_use]
243 pub fn contract_guards_are_exact(&self) -> bool {
244 match self {
245 Self::True | Self::Guard(_) => true,
246 Self::False | Self::Approximate { .. } => false,
247 Self::Not(inner) => negation_flattens_exactly(inner),
248 Self::And(predicates) | Self::Or(predicates) => {
249 predicates.iter().all(Self::contract_guards_are_exact)
250 }
251 }
252 }
253
254 fn collect_value_paths(&self, out: &mut BTreeSet<String>) {
255 match self {
256 Self::True | Self::False => {}
257 Self::Approximate { paths, .. } => out.extend(paths.iter().cloned()),
258 Self::Guard(guard) => {
259 for path in guard.value_paths() {
260 out.insert(path.to_string());
261 }
262 }
263 Self::Not(inner) => inner.collect_value_paths(out),
264 Self::And(predicates) | Self::Or(predicates) => {
265 for predicate in predicates {
266 predicate.collect_value_paths(out);
267 }
268 }
269 }
270 }
271
272 fn collect_conditionally_optional_paths(&self, out: &mut BTreeSet<String>) {
273 match self {
274 Self::Guard(Guard::NotEq { path, .. } | Guard::Absent { path }) => {
275 out.insert(path.clone());
276 }
277 Self::Not(inner) => match inner.as_ref() {
278 Self::Guard(Guard::Truthy { path }) => {
279 out.insert(path.clone());
280 }
281 _ => inner.collect_conditionally_optional_paths(out),
282 },
283 Self::Or(predicates) => {
284 for predicate in predicates {
285 out.extend(predicate.value_paths());
286 }
287 }
288 Self::And(predicates) => {
289 for predicate in predicates {
290 predicate.collect_conditionally_optional_paths(out);
291 }
292 }
293 Self::True
294 | Self::False
295 | Self::Approximate { .. }
296 | Self::Guard(
297 Guard::Truthy { .. }
298 | Guard::Eq { .. }
299 | Guard::MatchesPattern { .. }
300 | Guard::RangeKeyPrefix { .. }
301 | Guard::RangeKeyEquals { .. }
302 | Guard::RangeKeyMatches { .. }
303 | Guard::Range { .. }
304 | Guard::With { .. }
305 | Guard::Default { .. }
306 | Guard::TypeIs { .. }
307 | Guard::NotTypeIs { .. }
308 | Guard::Not { .. }
309 | Guard::Or { .. }
310 | Guard::AnyOf { .. }
311 | Guard::IntGt { .. }
312 | Guard::IntLt { .. }
313 | Guard::AtMostOneMember { .. }
314 | Guard::MinMembers { .. }
315 | Guard::HasKey { .. }
316 | Guard::ContainsEquals { .. },
317 ) => {}
318 }
319 }
320
321 #[must_use]
323 pub fn contract_guard_stack(predicates: &[Self]) -> Vec<Guard> {
324 let mut guards = Vec::new();
325 for predicate in predicates {
326 for guard in predicate.contract_guards() {
327 if !guards.contains(&guard) {
328 guards.push(guard);
329 }
330 }
331 }
332 guards
333 }
334
335 #[must_use]
337 pub fn map_value_paths<F>(self, map: &mut F) -> Self
338 where
339 F: FnMut(&str) -> String,
340 {
341 match self {
342 Self::True => Self::True,
343 Self::False => Self::False,
344 Self::Approximate {
345 marker,
346 paths,
347 sound_subset,
348 } => Self::Approximate {
349 marker,
350 paths: paths.into_iter().map(|path| map(&path)).collect(),
351 sound_subset: sound_subset
352 .into_iter()
353 .map(|guard| guard.map_value_paths(map))
354 .collect(),
355 },
356 Self::Guard(guard) => Self::Guard(guard.map_value_paths(map)),
357 Self::Not(inner) => Self::Not(Box::new(inner.map_value_paths(map))),
358 Self::And(predicates) => Self::And(
359 predicates
360 .into_iter()
361 .map(|predicate| predicate.map_value_paths(map))
362 .collect(),
363 ),
364 Self::Or(predicates) => Self::Or(
365 predicates
366 .into_iter()
367 .map(|predicate| predicate.map_value_paths(map))
368 .collect(),
369 ),
370 }
371 }
372}
373
374fn negation_flattens_exactly(inner: &Predicate) -> bool {
378 match inner {
379 Predicate::Guard(
380 Guard::Truthy { .. }
381 | Guard::With { .. }
382 | Guard::Not { .. }
383 | Guard::Or { .. }
384 | Guard::Eq { .. }
385 | Guard::NotEq { .. }
386 | Guard::TypeIs { .. }
387 | Guard::NotTypeIs { .. },
388 ) => true,
389 Predicate::Not(inner) => inner.contract_guards_are_exact(),
390 Predicate::And(predicates) | Predicate::Or(predicates) => {
391 predicates.iter().all(negation_flattens_exactly)
392 }
393 _ => false,
394 }
395}
396
397fn negated_contract_guards(inner: &Predicate) -> Vec<Guard> {
398 match inner {
399 Predicate::Guard(Guard::Truthy { path } | Guard::With { path }) => {
400 vec![Guard::Not { path: path.clone() }]
401 }
402 Predicate::Guard(Guard::Not { path }) => vec![Guard::Truthy { path: path.clone() }],
403 Predicate::Guard(Guard::Or { paths }) => paths
404 .iter()
405 .map(|path| Guard::Not { path: path.clone() })
406 .collect(),
407 Predicate::Guard(Guard::Eq { path, value }) => vec![Guard::NotEq {
408 path: path.clone(),
409 value: value.clone(),
410 }],
411 Predicate::Guard(Guard::NotEq { path, value }) => vec![Guard::Eq {
412 path: path.clone(),
413 value: value.clone(),
414 }],
415 Predicate::Guard(Guard::TypeIs { path, schema_type }) => vec![Guard::NotTypeIs {
416 path: path.clone(),
417 schema_type: schema_type.clone(),
418 }],
419 Predicate::Guard(Guard::NotTypeIs { path, schema_type }) => vec![Guard::TypeIs {
420 path: path.clone(),
421 schema_type: schema_type.clone(),
422 }],
423 Predicate::Not(inner) => inner.contract_guards(),
424 Predicate::Or(predicates) => {
428 let mut guards = Vec::new();
429 for predicate in predicates {
430 let negated = negated_contract_guards(predicate);
431 if negated.is_empty() {
432 return Vec::new();
433 }
434 guards.extend(negated);
435 }
436 guards
437 }
438 Predicate::And(predicates) => {
441 let mut alternatives = Vec::new();
442 for predicate in predicates {
443 let negated = negated_contract_guards(predicate);
444 if negated.is_empty() {
445 return Vec::new();
446 }
447 alternatives.push(negated);
448 }
449 alternatives_to_guards(alternatives)
450 }
451 _ => Vec::new(),
452 }
453}
454
455fn or_contract_guards(predicates: &[Predicate]) -> Vec<Guard> {
456 let alternatives = predicates
457 .iter()
458 .map(Predicate::contract_guards)
459 .collect::<Vec<_>>();
460
461 if alternatives.iter().any(Vec::is_empty) {
462 return Vec::new();
463 }
464 alternatives_to_guards(alternatives)
465}
466
467fn alternatives_to_guards(mut alternatives: Vec<Vec<Guard>>) -> Vec<Guard> {
471 for alternative in &mut alternatives {
472 alternative.sort();
473 alternative.dedup();
474 }
475 alternatives.sort();
476 alternatives.dedup();
477
478 if alternatives.len() == 1 {
479 return alternatives.pop().unwrap_or_default();
480 }
481
482 if let Some(paths) = truthy_or_paths(&alternatives) {
483 return vec![Guard::Or { paths }];
484 }
485
486 vec![Guard::AnyOf { alternatives }]
487}
488
489fn truthy_or_paths(alternatives: &[Vec<Guard>]) -> Option<Vec<String>> {
490 alternatives
491 .iter()
492 .map(|alternative| match alternative.as_slice() {
493 [Guard::Truthy { path }] => Some(path.clone()),
494 _ => None,
495 })
496 .collect()
497}
498
499#[cfg(test)]
500#[path = "tests/predicate.rs"]
501mod tests;