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}