shape/
case_enum.rs

1pub(crate) mod all;
2mod deduplicate_shape;
3pub(crate) mod one;
4
5use std::iter::{empty, once};
6
7use self::all::All;
8use self::one::One;
9use super::child_shape::NamedShapePathKey;
10use super::Shape;
11use crate::location::Located;
12use indexmap::IndexMap;
13
14/// The [`ShapeCase`] enum attempts to capture all the common shapes of JSON
15/// values, not just the JSON types themselves, but also patterns of usage (such
16/// as using JSON arrays as either static tuples or dynamic lists).
17///
18/// A [`ShapeCase`] enum variant like [`ShapeCase::One`] may temporarily
19/// represent a structure that has not been fully simplified, but simplication
20/// is required to turn the [`ShapeCase`] into a `Shape`, using the
21/// `ShapeCase::simplify(&self) -> Shape` method.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub enum ShapeCase {
24    /// The `Bool`, `String`, and `Int` variants can either represent a general
25    /// shape using `None` for the `Option`, or a specific shape using `Some`.
26    /// For example, `ShapeCase::Bool(None)` is a shape satisfied by either true
27    /// or false, like the Rust bool type, while `ShapeCase::Bool(Some(true))`
28    /// is a shape satisfied only by `true` (and likewise for `false`).
29    Bool(Option<bool>),
30
31    /// Similar to [`ShapeCase::Bool`], [`ShapeCase::String`] represents a shape
32    /// matching either any string (in the `None` case) or some specific string
33    /// (in the `Some` case).
34    String(Option<String>),
35
36    /// Similar to [`ShapeCase::Bool`] and [`ShapeCase::String`],
37    /// [`ShapeCase::Int`] can represent either any integer or some specific
38    /// integer.
39    Int(Option<i64>),
40
41    /// [`ShapeCase::Float`] is a shape that captures the set of all floating
42    /// point numbers. We do not allow singleton floating point value shapes,
43    /// since floating point equality is unreliable.
44    Float,
45
46    /// [`ShapeCase::Null`] is a singleton shape whose only possible value is
47    /// `null`. Note that [`ShapeCase::Null`] is different from
48    /// [`ShapeCase::None`], which represents the absence of a value.
49    Null,
50
51    /// [`ShapeCase::Array`] represents a `prefix` of statically known shapes
52    /// (like a tuple type), followed by an optional `tail` shape for all other
53    /// (dynamic) elements. When only the `prefix` elements are defined, the
54    /// `tail` shape is [`ShapeCase::None`]. `ShapeCase::Array(vec![],
55    /// ShapeCase::None)` is the shape of an empty array.
56    Array { prefix: Vec<Shape>, tail: Shape },
57
58    /// [`ShapeCase::Object`] is a map of statically known field names to field
59    /// shapes, together with an optional type for all other string keys. When
60    /// dynamic string indexing is disabled, the rest shape will be
61    /// [`ShapeCase::None`]. Note that accessing the dynamic map always returns
62    /// `ShapeCase::One([rest_shape, ShapeCase::None])`, to reflect the
63    /// uncertainty of the shape of dynamic keys not present in the static
64    /// fields.
65    Object {
66        // TODO: track location of keys
67        fields: IndexMap<String, Shape>,
68        rest: Shape,
69    },
70
71    /// A union of shapes, satisfied by values that satisfy any of the shapes in
72    /// the set.
73    ///
74    /// Create using [`Shape::one`].
75    One(One),
76
77    /// An intersection of shapes, satisfied by values that satisfy all of the
78    /// shapes in the set. When applied to multiple [`ShapeCase::Object`]
79    /// shapes, [`ShapeCase::All`] represents merging the fields of the objects,
80    /// as reflected by the simplification logic.
81    ///
82    /// Crete using [`Shape::all`].
83    All(All),
84
85    /// [`ShapeCase::Name`] refers to a shape declared with the given name,
86    /// possibly in the future. When shape processing needs to refer to the
87    /// shape of some subproperty of a named shape, it uses the
88    /// `Vec<NamedShapePathKey>` subpath to represent the nested shape. When the
89    /// named shape is eventually declared, the subpath can be used to resolve
90    /// the actual shape of the nested property path.
91    //
92    /// As of now, the name `String` is expected to be either an identifier or a
93    /// variable name like `$root` or `$this` or `$args`, allowing
94    /// [`ShapeCase::Name`] to represent types of subproperties of variables as
95    /// well as named types from some schema.
96    Name(Located<String>, Vec<Located<NamedShapePathKey>>),
97
98    /// [`ShapeCase::Unknown`] is a shape that represents any possible JSON
99    /// value (including `ShapeCase::None`).
100    Unknown,
101
102    /// Represents the absence of a value, or the shape of a property not
103    /// present in an object. Used by the rest parameters of both
104    /// [`ShapeCase::Array`] and [`ShapeCase::Object`] to indicate no additional
105    /// dynamic elements are allowed, and with [`ShapeCase::One`] to represent
106    /// optionality of values, e.g. `One<Bool, None>`.
107    None,
108
109    /// Represents a local failure of shape processing.
110    Error(Error),
111}
112
113impl ShapeCase {
114    #[must_use]
115    pub fn is_none(&self) -> bool {
116        matches!(self, Self::None)
117    }
118
119    #[must_use]
120    pub fn is_null(&self) -> bool {
121        matches!(self, Self::Null)
122    }
123
124    #[must_use]
125    pub fn error(message: String) -> Self {
126        Self::Error(Error {
127            message,
128            partial: None,
129        })
130    }
131
132    #[must_use]
133    pub fn error_with_partial(message: String, partial: Shape) -> Self {
134        Self::Error(Error {
135            message,
136            partial: Some(partial),
137        })
138    }
139
140    /// Iterate over all errors within this shape, recursively
141    pub fn errors(&self) -> Box<dyn Iterator<Item = &Error> + '_> {
142        match self {
143            Self::Error(error) => Box::new(once(error)),
144            Self::Array { prefix, tail } => {
145                Box::new(prefix.iter().flat_map(Shape::errors).chain(tail.errors()))
146            }
147            Self::Object { fields, rest } => {
148                Box::new(fields.values().flat_map(Shape::errors).chain(rest.errors()))
149            }
150            Self::One(shapes) => Box::new(shapes.iter().flat_map(Shape::errors)),
151            Self::All(shapes) => Box::new(shapes.iter().flat_map(Shape::errors)),
152            _ => Box::new(empty()),
153        }
154    }
155}
156
157impl From<bool> for ShapeCase {
158    fn from(value: bool) -> Self {
159        ShapeCase::Bool(Some(value))
160    }
161}
162
163impl From<String> for ShapeCase {
164    fn from(value: String) -> Self {
165        ShapeCase::String(Some(value))
166    }
167}
168
169impl From<&str> for ShapeCase {
170    fn from(value: &str) -> Self {
171        ShapeCase::String(Some(value.to_string()))
172    }
173}
174
175impl From<i64> for ShapeCase {
176    fn from(value: i64) -> Self {
177        ShapeCase::Int(Some(value))
178    }
179}
180
181impl PartialEq<&ShapeCase> for ShapeCase {
182    fn eq(&self, other: &&ShapeCase) -> bool {
183        self == *other
184    }
185}
186
187impl PartialEq<ShapeCase> for &ShapeCase {
188    fn eq(&self, other: &ShapeCase) -> bool {
189        *self == other
190    }
191}
192
193/// Represents a local failure of shape processing.
194#[derive(Clone, Debug, Eq, PartialEq)]
195pub struct Error {
196    pub message: String,
197    /// This `partial` shape can be another `ShapeCase::Error` shape, which
198    /// allows for chaining of multiple errors.
199    pub partial: Option<Shape>,
200}
201
202impl From<Error> for ShapeCase {
203    fn from(error: Error) -> Self {
204        ShapeCase::Error(error)
205    }
206}