shape/accepts.rs
1use crate::case_enum::Error;
2use crate::child_shape::NamedShapePathKey;
3
4use super::Shape;
5use super::ShapeCase;
6
7/// The `Shape::validate*` methods return a vector of `ShapeMismatch` errors
8/// that is non-empty when validation fails.
9///
10/// Each `ShapeMismatch` error contains the `expected` shape, the `received`
11/// shape, and a `path` of `NamedShapePathKey` elements that leads from the root
12/// shape to the nested object/array location where the mismatch happened.
13///
14/// Conveniently, the `expected`/`received` terminology works for both the
15/// `expected.accepts(received)` and `received.satisfies(expected)` directions
16/// of comparison, so it doesn't matter that `ShapeMismatch` errors happen to be
17/// generated by the internal `satisfies_at_path` method rather than by the
18/// `validate` method (which would also work).
19///
20/// Once issue #5 is implemented, you'll be able to obtain source location
21/// information from the `expected` and `received` shapes (instead of adding
22/// additional fields to the `ShapeMismatch` struct).
23#[derive(Debug, PartialEq, Eq, Clone, Hash)]
24pub struct ShapeMismatch {
25 pub expected: Shape,
26 pub received: Shape,
27 pub path: Vec<NamedShapePathKey>,
28}
29
30impl Shape {
31 /// Returns true if the other shape meets all the expectations of the self
32 /// shape. In set theory terms, the set of all values accepted by other is a
33 /// subset of the set of all values accepted by self.
34 #[must_use]
35 pub fn accepts(&self, other: &Shape) -> bool {
36 other.satisfies(self)
37 }
38
39 /// Returns true iff the given [`serde_json::Value`] satisfies self.
40 #[must_use]
41 pub fn accepts_json(&self, json: &serde_json::Value) -> bool {
42 self.accepts(&Shape::from_json(json))
43 }
44
45 /// Returns true iff the given [`serde_json_bytes::Value`] satisfies self.
46 pub fn accepts_json_bytes(&self, json: &serde_json_bytes::Value) -> bool {
47 self.accepts(&Shape::from_json_bytes(json))
48 }
49
50 /// Validates that all expectations of the `self` shape are met by the
51 /// `to_be_validated` shape, erroring with a non-empty vector of
52 /// `ShapeMismatch` errors when validation fails.
53 ///
54 /// Note that `validate` is closer to `accepts` than `satisfies` in terms of
55 /// the direction of the comparison, which is why the implementation uses
56 /// `to_be_validated.satisfies_at_path(self, ...)` rather than
57 /// `self.satisfies_at_path(to_be_validated, ...)`.
58 #[must_use]
59 pub fn validate(&self, to_be_validated: &Shape) -> Vec<ShapeMismatch> {
60 to_be_validated.satisfies_at_path(self, &mut Vec::new())
61 }
62
63 /// `Shape::validate_json` is to `Shape::validate` as `Shape::accepts_json`
64 /// is to `Shape::accepts`.
65 #[must_use]
66 pub fn validate_json(&self, json: &serde_json::Value) -> Vec<ShapeMismatch> {
67 self.validate(&Shape::from_json(json))
68 }
69
70 /// `Shape::validate_json_bytes` is to `Shape::validate` as
71 /// `Shape::accepts_json_bytes` is to `Shape::accepts`.
72 pub fn validate_json_bytes(&self, json: &serde_json_bytes::Value) -> Vec<ShapeMismatch> {
73 self.validate(&Shape::from_json_bytes(json))
74 }
75
76 /// Returns `true` if the `self` shape meets all the expectations of the
77 /// `other` shape. In set theory terms, `self` satisfying `other` means the
78 /// set of all values accepted by `self` is a subset of the set of all
79 /// values accepted by `other`.
80 ///
81 /// The `satisfies` method is the inverse of the `accepts` method, in the
82 /// sense that `a.accepts(b)` is equivalent to `b.satisfies(a)`. For
83 /// historical reasons, the bulk of the `accepts`/`satisfies` logic happens
84 /// in the internal `satisfies_at_path` method, though I have since realized
85 /// the `accepts` direction generalizes a bit better to situations where
86 /// `other` is not a `Shape`, such as `Shape::accepts_json(&self, json:
87 /// &serde_json::Value)`.
88 #[must_use]
89 pub fn satisfies(&self, other: &Shape) -> bool {
90 self.satisfies_at_path(other, &mut Vec::new()).is_empty()
91 }
92
93 #[allow(clippy::too_many_lines)]
94 fn satisfies_at_path(
95 &self,
96 other: &Shape,
97 path: &mut Vec<NamedShapePathKey>,
98 ) -> Vec<ShapeMismatch> {
99 let mut mismatches = Vec::new();
100
101 // Helper closure that translates boolean results to vector results. The
102 // `path` and `mismatches` parameters are always the local parameter and
103 // variable of the same names from the outer scope, but passing them in
104 // this way simplifies ownership logic and strips mutability.
105 let report = |satisfies: bool,
106 path: &[NamedShapePathKey],
107 mismatches: Vec<ShapeMismatch>|
108 -> Vec<ShapeMismatch> {
109 if satisfies {
110 // Use the report closure only in situations where the truth of
111 // `satisfies` should hide any previous mismatches.
112 vec![]
113 } else {
114 // Concatenate earlier mismatches with the current mismatch.
115 [
116 mismatches,
117 vec![ShapeMismatch {
118 expected: other.clone(),
119 received: self.clone(),
120 path: path.to_vec(),
121 }],
122 ]
123 .concat()
124 }
125 };
126
127 match other.case() {
128 ShapeCase::One(other_shapes) => {
129 // If the union is empty (i.e. other.is_never()), then the only
130 // self shapes that can satisfy this empty union are other never
131 // shapes and None.
132 if other_shapes.is_empty() {
133 return if self.is_never() || self.is_none() {
134 Vec::new()
135 } else {
136 // Reporting this error is important, since there are no
137 // self shapes to produce mismatches below.
138 vec![ShapeMismatch {
139 expected: other.clone(),
140 received: self.clone(),
141 path: path.clone(),
142 }]
143 };
144 }
145
146 // If self satisfies any single shape in the union, then self
147 // satisfies the union.
148 for other_shape in other_shapes {
149 let other_mismatches = self.satisfies_at_path(other_shape, path);
150 if other_mismatches.is_empty() {
151 return Vec::new();
152 }
153 mismatches.extend(other_mismatches);
154 }
155 // Importantly, we do not return false here (in contrast with
156 // the ::All case below), because the union might still be
157 // satisfied by self, e.g. when self is Bool and the union
158 // contains both true and false as members.
159 //
160 // We will need to handle ShapeCase::One logic in the ::Bool
161 // case below, but otherwise the loop above saves most of the
162 // cases below from explicitly worrying about other being a
163 // ShapeCase::One.
164 }
165
166 ShapeCase::All(other_shapes) => {
167 // If other is an intersection, then self must satisfy all of
168 // the member shapes. Returning unconditionally from this case
169 // means we don't have to worry about other being a
170 // ShapeCase::All below.
171 return other_shapes
172 .iter()
173 .flat_map(|other_shape| self.satisfies_at_path(other_shape, path))
174 .collect::<Vec<_>>();
175 }
176
177 // Sometimes we don't know anything (yet) about the structure of a
178 // shape, but still want to refer to it by name and access the
179 // shapes of subproperties (symbolically if not concretely). That's
180 // what the ShapeCase::Name variant models.
181 //
182 // The ShapeCase::Name name parameter is either an identifier
183 // referring to a named shape (like ID or JSON or Product) or a
184 // string like "$this" when referring to the type of a variable.
185 //
186 // When it comes to ShapeCase::satisfies, we have a policy decision
187 // to make here: are named shape references satisfied by any shape,
188 // or satisfied by none? Do they satisfy any shape, or satisfy none?
189 //
190 // By policy (and because it works well in practice) we say named
191 // shape references are satisfied by any shape, but satisfy no
192 // shape. The _expectations_ narrative may help here: a named shape
193 // imposes no expectations on shapes that seek to satisfy it, so all
194 // succeed; however, a named shape also meets no expectations of any
195 // other shape, except (trivially) itself. This interpretation would
196 // make named shape references behave like the `unknown` type in
197 // TypeScript, which is satisfied by all but satisfies none.
198 //
199 // Given this way of handling named shape references, we
200 // conveniently do not need a separate enum variant to represent the
201 // general JSON type, as it is just another named shape reference,
202 // akin to `unknown` in TypeScript.
203 ShapeCase::Name(_name, _subpath) => {
204 // When other is a named shape reference, any self satisfies it.
205 return Vec::new();
206 }
207
208 ShapeCase::Unknown => {
209 // Unknown shapes are satisfied by any shape.
210 return Vec::new();
211 }
212
213 ShapeCase::Error(Error { partial, .. }) => {
214 // If other is an error shape, it accepts (is satisfied by)
215 // itself trivially.
216 if self.case == other.case {
217 return Vec::new();
218 }
219
220 // If the other error has Some(partial) shape, self satisfies
221 // other if it satisfies that partial shape.
222 if let Some(partial) = partial {
223 return self.satisfies_at_path(partial, path);
224 }
225
226 // Otherwise, the other error shape is not satisfied by self.
227 return vec![ShapeMismatch {
228 expected: other.clone(),
229 received: self.clone(),
230 path: path.clone(),
231 }];
232 }
233
234 // None shapes are satisfied only by themselves and the empty union
235 // produced by Shape::never.
236 ShapeCase::None => {
237 return if self.is_none() || self.is_never() {
238 Vec::new()
239 } else {
240 vec![ShapeMismatch {
241 expected: other.clone(),
242 received: self.clone(),
243 path: path.clone(),
244 }]
245 };
246 }
247
248 _ => {
249 // Fall through to matching against self...
250 }
251 }
252
253 match self.case() {
254 ShapeCase::Bool(Some(value)) => report(
255 match other.case() {
256 ShapeCase::Bool(other_value) => {
257 Some(value) == other_value.as_ref() || other_value.is_none()
258 }
259 // We already handled the ::One and ::All cases above.
260 _ => false,
261 },
262 path,
263 mismatches,
264 ),
265
266 ShapeCase::Bool(None) => report(
267 (|| match other.case() {
268 ShapeCase::Bool(None) => true,
269
270 // This case goes beyond the basic ShapeCase::One handling
271 // provided at the top of the function, because we want to allow
272 // ::Bool(None) to match a union of true and false.
273 ShapeCase::One(other_shapes) => {
274 let true_shape = Shape::bool_value(true, []);
275 let false_shape = Shape::bool_value(false, []);
276 let mut true_satisfies = false;
277 let mut false_satisfies = false;
278
279 for other_shape in other_shapes {
280 // We already tested whether self.satisfies(other_shape)
281 // above, so we don't need to retest that here.
282 if true_shape.satisfies_at_path(other_shape, path).is_empty() {
283 true_satisfies = true;
284 }
285 if false_shape.satisfies_at_path(other_shape, path).is_empty() {
286 false_satisfies = true;
287 }
288 // If both true and false satisfy at least one
289 // other_shape, then the union is satisfied by Bool.
290 if true_satisfies && false_satisfies {
291 // Returns from the (|| match ...)() closure.
292 return true;
293 }
294 }
295
296 false
297 }
298
299 _ => false,
300 })(),
301 path,
302 mismatches,
303 ),
304
305 ShapeCase::String(value) => report(
306 match other.case() {
307 ShapeCase::String(other_value) => value == other_value || other_value.is_none(),
308 // We already handled the ::One case above.
309 _ => false,
310 },
311 path,
312 mismatches,
313 ),
314
315 ShapeCase::Int(value) => report(
316 match other.case() {
317 ShapeCase::Int(other_value) => value == other_value || other_value.is_none(),
318 // All Int values are also (trivially convertible to) Float
319 // values, so Int is a subshape of Float.
320 ShapeCase::Float => true,
321 // We already handled the ::One case above.
322 _ => false,
323 },
324 path,
325 mismatches,
326 ),
327
328 ShapeCase::Float => report(matches!(other.case(), ShapeCase::Float), path, mismatches),
329
330 // Both ::Null and ::None satisfy only themselves, but they have an
331 // important difference in behavior when they appear in ::All
332 // intersections. The null value "poisons" intersections, reducing
333 // the whole intersection to null, which can be appropriate behavior
334 // when reporting certain kinds of top-level errors. The None value
335 // simply disappears from intersections, as it imposes no additional
336 // constraints on the intersection shape.
337 ShapeCase::Null => report(matches!(other.case(), ShapeCase::Null), path, mismatches),
338
339 // None is satisfied by itself and the unsatisfiable empty ::One
340 // union produced by Shape::never.
341 ShapeCase::None => report(other.is_none() || other.is_never(), path, mismatches),
342
343 // ShapeCase::Unknown satisfies no other shapes except itself. We
344 // already handled the case when other is ::Unknown above, so we can
345 // report false here.
346 ShapeCase::Unknown => report(false, path, mismatches),
347
348 ShapeCase::Array { prefix, tail } => match other.case() {
349 ShapeCase::Array {
350 prefix: other_prefix,
351 tail: other_tail,
352 } => {
353 let mismatch_count = mismatches.len();
354
355 for (i, other_shape) in other_prefix.iter().enumerate() {
356 path.push(NamedShapePathKey::from(i));
357 if let Some(shape) = prefix.get(i) {
358 mismatches.extend(shape.satisfies_at_path(other_shape, path));
359 } else {
360 mismatches.extend(tail.satisfies_at_path(other_shape, path));
361 }
362 path.pop();
363 }
364
365 if mismatches.len() > mismatch_count {
366 return mismatches;
367 }
368
369 #[allow(clippy::match_same_arms)]
370 match (tail.case(), other_tail.case()) {
371 // If neither self nor other have a rest shape (and
372 // prefix satisfies other_prefix), self satisfies other.
373 (ShapeCase::None, ShapeCase::None) => Vec::new(),
374 // If self has a rest shape but other does not, other
375 // has no expectations about self's rest shape (and we
376 // already checked prefixes), so self satisfies other.
377 (_, ShapeCase::None) => Vec::new(),
378 // If self has no rest shape while other does, self will
379 // always be a static tuple of elements that we know
380 // satisfies other. Other may permit additional rest
381 // elements, but their absence does not prevent
382 // satisfaction.
383 (ShapeCase::None, _) => Vec::new(),
384 // If ShapeCase::None is not involved on either side,
385 // then other expects a certain rest shape and self has
386 // its own rest shape, so rest must satisfy other_tail.
387 _ => {
388 path.push(NamedShapePathKey::AnyIndex);
389 mismatches.extend(tail.satisfies_at_path(other_tail, path));
390 path.pop();
391
392 if mismatches.len() > mismatch_count {
393 mismatches
394 } else {
395 Vec::new()
396 }
397 }
398 }
399 }
400
401 // We already handled the ::One and ::All cases above.
402 _ => report(false, path, mismatches),
403 },
404
405 ShapeCase::Object { fields, rest } => match other.case() {
406 ShapeCase::Object {
407 fields: other_fields,
408 rest: other_rest,
409 } => {
410 let mismatch_count = mismatches.len();
411
412 // Check that all fields expected by other are present in
413 // self.
414 for (field_name, field_shape) in other_fields {
415 path.push(NamedShapePathKey::from(field_name.as_str()));
416 if let Some(self_field_shape) = fields.get(field_name) {
417 mismatches
418 .extend(self_field_shape.satisfies_at_path(field_shape, path));
419 } else {
420 // Optional properties of shape S can be represented
421 // by Shape::one(&[S, Shape::none()]) field shapes
422 // in other_fields, which Shape::none() (i.e. the
423 // shape of the undefined field in self) satisfies.
424 mismatches.extend(Shape::none().satisfies_at_path(field_shape, path));
425 }
426 path.pop();
427 }
428
429 if mismatches.len() > mismatch_count {
430 return mismatches;
431 }
432
433 #[allow(clippy::match_same_arms)]
434 match (rest.case(), other_rest.case()) {
435 // If neither self nor other have a rest shape (and fields
436 // satisfies other_fields), self satisfies other.
437 (ShapeCase::None, ShapeCase::None) => Vec::new(),
438 // If self has a rest shape but other does not, other has
439 // no expectations about self's rest shape (and we already
440 // checked static fields), so self satisfies other.
441 (_, ShapeCase::None) => Vec::new(),
442 // If self has no rest shape while other does, self will
443 // always be a static set of fields that we know
444 // satisfies other. Other may permit additional rest
445 // properties, but their absence does not prevent
446 // satisfaction.
447 (ShapeCase::None, _) => Vec::new(),
448 // If ShapeCase::None is not involved on either side,
449 // then other expects a certain rest shape and self has
450 // its own rest shape, so rest must satisfy other_rest.
451 _ => {
452 path.push(NamedShapePathKey::AnyField);
453 mismatches.extend(rest.satisfies_at_path(other_rest, path));
454 path.pop();
455
456 if mismatches.len() > mismatch_count {
457 mismatches
458 } else {
459 Vec::new()
460 }
461 }
462 }
463 }
464
465 // We already handled the ::One and ::All cases above.
466 _ => report(false, path, mismatches),
467 },
468
469 // If *self* is a ShapeCase::One union, then every possibility must
470 // satisfy other. For example, if self is true | false, and other is
471 // Bool, then since true and false each individually satisfy Bool,
472 // the union true | false satisfies Bool.
473 ShapeCase::One(shapes) => {
474 if shapes.is_empty() {
475 if other.is_never() || other.is_none() {
476 Vec::new()
477 } else {
478 vec![ShapeMismatch {
479 expected: other.clone(),
480 received: self.clone(),
481 path: path.clone(),
482 }]
483 }
484 } else {
485 let mismatch_count = mismatches.len();
486 for shape in shapes {
487 mismatches.extend(shape.satisfies_at_path(other, path));
488 }
489 if mismatches.len() > mismatch_count {
490 mismatches
491 } else {
492 Vec::new()
493 }
494 }
495 }
496
497 // If self is a ShapeCase::All intersection, then it satisfies other
498 // if any of the member shapes satisfy other.
499 //
500 // Tricky example involving an unsimplified ShapeCase::All:
501 // { a: Int } & { b: Int } should satisfy { a: Int, b: Int }
502 ShapeCase::All(shapes) => {
503 for shape in shapes {
504 let other_mismatches = self.satisfies_at_path(shape, path);
505 if other_mismatches.is_empty() {
506 return other_mismatches;
507 }
508 mismatches.extend(other_mismatches);
509 }
510 mismatches
511 }
512
513 ShapeCase::Error(Error { partial, .. }) => {
514 // We already tested whether self == other when other is an
515 // error shape above, so we only need to test the partial shape
516 // in the other direction here.
517 //
518 // If self is an error shape, it satisfies itself trivially, and
519 // if it has Some(partial) shape, the error satisfies other if
520 // that partial shape does.
521 //
522 // In cases when the partial shape represents a best guess at
523 // the intended shape, mismatches can provide guidance without
524 // interfering with satisfaction logic.
525 if self.case == other.case {
526 Vec::new()
527 } else if let Some(partial) = partial {
528 partial.satisfies_at_path(other, path)
529 } else {
530 report(false, path, mismatches)
531 }
532 }
533
534 ShapeCase::Name(_name, _subpath) => {
535 // When self is a named shape reference, it satisfies no other
536 // except itself. This makes named shape references more like
537 // TypeScript's `unknown` type than its `any` type (which
538 // satisfies all and is satisfied by all).
539 report(self.case == other.case, path, mismatches)
540 }
541 }
542 }
543}