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}