shape/
child_shape.rs

1use super::Shape;
2use super::ShapeCase;
3use crate::location::Location;
4use crate::name::Name;
5use crate::name::NameCase;
6
7impl Shape {
8    /// Returns a new [`Shape`] representing the shape of a given subproperty
9    /// (field name) of the `self` shape.
10    #[must_use]
11    pub fn field(&self, field_name: &str, locations: impl IntoIterator<Item = Location>) -> Shape {
12        let locations = locations.into_iter().collect::<Vec<_>>();
13
14        let child_shape = match self.case() {
15            ShapeCase::Object { fields, rest } => {
16                if let Some(shape) = fields.get(field_name) {
17                    shape.clone().with_locations(locations.iter())
18                } else {
19                    // The rest shape might be ShapeCase::None, so the
20                    // ShapeCase::One will simplify to just ShapeCase::None.
21                    Shape::one(
22                        [rest.clone().with_locations(locations.iter()), Shape::none()],
23                        locations.clone(),
24                    )
25                }
26            }
27
28            ShapeCase::Array { prefix, tail } => {
29                // Following GraphQL logic, map key over the array and make
30                // a new ShapeCase::Array with the resulting shapes.
31                let new_items = prefix
32                    .iter()
33                    .map(|shape| shape.field(field_name, locations.clone()))
34                    .collect::<Vec<_>>();
35
36                let new_rest = tail.field(field_name, locations.clone());
37
38                // If we tried mapping a field name over an array, and all
39                // we got back was an empty array, then we can simplify to
40                // ShapeCase::None.
41                if new_rest.is_none() && new_items.iter().all(Shape::is_none) {
42                    Shape::none()
43                } else {
44                    Shape::array(new_items, new_rest, locations.clone())
45                }
46            }
47
48            ShapeCase::One(shapes) => Shape::one(
49                shapes
50                    .iter()
51                    .map(|shape| shape.field(field_name, locations.clone())),
52                locations.clone(),
53            ),
54
55            ShapeCase::All(shapes) => Shape::all(
56                shapes
57                    .iter()
58                    .map(|shape| shape.field(field_name, locations.clone())),
59                locations.clone(),
60            ),
61
62            ShapeCase::Name(name, weak) => {
63                if let Some(named_shape) = weak.upgrade(name) {
64                    named_shape.field(field_name, locations.clone())
65                } else {
66                    Shape::new(
67                        ShapeCase::Name(name.field(field_name, locations.clone()), weak.clone()),
68                        locations.clone(),
69                    )
70                }
71            }
72
73            ShapeCase::Unknown => self.clone().with_locations(locations.iter()),
74
75            _ => Shape::none(),
76        };
77
78        self.propagate_names_to_child(child_shape.with_locations(locations.iter()), |name| {
79            name.field(field_name, locations.clone())
80        })
81    }
82
83    fn propagate_names_to_child(
84        &self,
85        child_shape: Shape,
86        name_mapper: impl Fn(&Name) -> Name,
87    ) -> Shape {
88        let mapped_names = self.names().map(name_mapper);
89        let mut named = child_shape;
90        for name in mapped_names {
91            named = named.with_name(&name);
92        }
93        named
94    }
95
96    /// Returns a new [`Shape`] representing the union of all field shapes of
97    /// object shapes, or just the shape itself for non-object shapes.
98    #[must_use]
99    pub fn any_field(&self, locations: impl IntoIterator<Item = Location>) -> Shape {
100        let locations = locations.into_iter().collect::<Vec<_>>();
101
102        let child_shape = match self.case() {
103            ShapeCase::Object { fields, rest } => {
104                let mut subshapes = Vec::new();
105                for shape in fields.values() {
106                    subshapes.push(shape.clone());
107                }
108                if !rest.is_none() {
109                    subshapes.push(rest.clone());
110                }
111                Shape::one(subshapes, locations.clone())
112            }
113
114            ShapeCase::Array { prefix, tail } => Shape::array(
115                prefix
116                    .iter()
117                    .map(|shape| shape.any_field(locations.clone())),
118                tail.any_field(locations.clone()),
119                locations.clone(),
120            ),
121
122            ShapeCase::One(shapes) => Shape::one(
123                shapes
124                    .iter()
125                    .map(|shape| shape.any_field(locations.clone())),
126                locations.clone(),
127            ),
128
129            ShapeCase::All(shapes) => Shape::all(
130                shapes
131                    .iter()
132                    .map(|shape| shape.any_field(locations.clone())),
133                locations.clone(),
134            ),
135
136            ShapeCase::Name(name, weak) => {
137                if let Some(named_shape) = weak.upgrade(name) {
138                    named_shape.any_field(locations.clone())
139                } else {
140                    // If the name is not bound to a shape, we create a new
141                    // ShapeCase::Name with the wildcard applied.
142                    Shape::new(
143                        ShapeCase::Name(name.any_field(locations.clone()), weak.clone()),
144                        locations.clone(),
145                    )
146                }
147            }
148
149            // Non-object, non-array shapes are returned as-is by the .**
150            // wildcard, with the additional locations (if any).
151            _ => self.clone(),
152        };
153
154        // Since the shape.any_field method returns a union of preexisting
155        // shapes, which should already have had any names that were given to
156        // self propagated appropriately to them, we do not need to call
157        // self.propagate_names_to_child here just to assign MyObject.** as a
158        // low-quality name to every member of the union.
159        child_shape.with_locations(locations.iter())
160    }
161
162    /// Returns a new [`Shape`] representing the shape of a given element of an
163    /// array shape.
164    #[must_use]
165    pub fn item(&self, index: usize, locations: impl IntoIterator<Item = Location>) -> Shape {
166        let locations = locations.into_iter().collect::<Vec<_>>();
167
168        let child_shape = match self.case() {
169            ShapeCase::Array { prefix, tail } => {
170                if let Some(shape) = prefix.get(index) {
171                    shape.clone().with_locations(locations.iter())
172                } else {
173                    // The rest shape might be ShapeCase::None, so the
174                    // ShapeCase::One will simplify to just ShapeCase::None.
175                    Shape::one([tail.clone(), Shape::none()], locations.clone())
176                }
177            }
178
179            ShapeCase::String(value) => {
180                if let Some(singleton) = value {
181                    if let Some(ch) = singleton.chars().nth(index) {
182                        Shape::string_value(ch.to_string().as_str(), locations.clone())
183                    } else {
184                        Shape::none()
185                    }
186                } else {
187                    Shape::one(
188                        [Shape::string(locations.clone()), Shape::none()],
189                        locations.clone(),
190                    )
191                }
192            }
193
194            ShapeCase::One(shapes) => Shape::one(
195                shapes
196                    .iter()
197                    .map(|shape| shape.item(index, locations.clone())),
198                locations.clone(),
199            ),
200
201            ShapeCase::All(shapes) => Shape::all(
202                shapes
203                    .iter()
204                    .map(|shape| shape.item(index, locations.clone())),
205                locations.clone(),
206            ),
207
208            ShapeCase::Name(name, weak) => {
209                if let Some(named_shape) = weak.upgrade(name) {
210                    named_shape.item(index, locations.clone())
211                } else {
212                    Shape::new(
213                        ShapeCase::Name(name.item(index, locations.clone()), weak.clone()),
214                        locations.clone(),
215                    )
216                }
217            }
218
219            ShapeCase::Unknown => self.clone(),
220
221            _ => Shape::none(),
222        };
223
224        self.propagate_names_to_child(child_shape.with_locations(locations.iter()), |name| {
225            name.item(index, locations.clone())
226        })
227    }
228
229    /// Returns a new [`Shape`] representing the union of all element shapes of
230    /// array shapes, or just the shape itself for non-array shapes.
231    #[must_use]
232    pub fn any_item(&self, locations: impl IntoIterator<Item = Location>) -> Shape {
233        let locations = locations.into_iter().collect::<Vec<_>>();
234
235        let child_shape = match self.case() {
236            ShapeCase::Array { prefix, tail } => {
237                let mut subshapes = Vec::new();
238                for shape in prefix {
239                    subshapes.push(shape.clone());
240                }
241                if !tail.is_none() {
242                    subshapes.push(tail.clone());
243                }
244                Shape::one(subshapes, locations.clone())
245            }
246
247            ShapeCase::String(value) => {
248                if let Some(singleton) = value {
249                    let mut subshapes = Vec::new();
250                    for ch in singleton.chars() {
251                        subshapes.push(Shape::string_value(ch.to_string().as_str(), []));
252                    }
253                    Shape::one(subshapes, locations.clone())
254                } else {
255                    Shape::string(locations.clone())
256                }
257            }
258
259            ShapeCase::One(shapes) => Shape::one(
260                shapes.iter().map(|shape| shape.any_item(locations.clone())),
261                locations.clone(),
262            ),
263
264            ShapeCase::All(shapes) => Shape::all(
265                shapes.iter().map(|shape| shape.any_item(locations.clone())),
266                locations.clone(),
267            ),
268
269            ShapeCase::Name(name, weak) => {
270                if let Some(named_shape) = weak.upgrade(name) {
271                    named_shape.any_item(locations.clone())
272                } else {
273                    Shape::new(
274                        ShapeCase::Name(name.any_item(locations.clone()), weak.clone()),
275                        locations.clone(),
276                    )
277                }
278            }
279
280            // Non-array shapes are returned as-is by the .* wildcard.
281            _ => self.clone(),
282        };
283
284        // Since the shape.any_item method returns a union of preexisting
285        // shapes, which should already have had any names that were given to
286        // self propagated appropriately to them, we do not need to call
287        // self.propagate_names_to_child here just to assign SomeArray.* as a
288        // low-quality name to every member of the union.
289        child_shape.with_locations(locations.iter())
290    }
291
292    /// Returns a new [`Shape`] representing the input shape with any
293    /// possibility of `null` replaced by `None`, but otherwise unchanged. This
294    /// models the behavior of a `?` optional chainining operator, which
295    /// additionally silences some errors related to missing fields at runtime.
296    /// When a [`ShapeCase::Name`] shape reference has a `?` step in its
297    /// subpath, that `?` step can be applied to the shape when/if the named
298    /// shape is declared/resolved, so the effect of the `?` is not lost.
299    #[must_use]
300    pub fn question(&self, locations: impl IntoIterator<Item = Location>) -> Shape {
301        let locations = locations.into_iter().collect::<Vec<_>>();
302
303        let child_shape = match self.case() {
304            ShapeCase::Null => Shape::none().with_locations(locations.iter()),
305
306            ShapeCase::One(shapes) => Shape::one(
307                shapes.iter().map(|shape| shape.question(locations.clone())),
308                locations.clone(),
309            ),
310
311            ShapeCase::All(shapes) => Shape::all(
312                shapes.iter().map(|shape| shape.question(locations.clone())),
313                locations.clone(),
314            ),
315
316            ShapeCase::Name(name, weak) => {
317                if let Some(named_shape) = weak.upgrade(name) {
318                    named_shape.question(locations.clone())
319                } else {
320                    Shape::new(
321                        ShapeCase::Name(name.question(locations.clone()), weak.clone()),
322                        locations.clone(),
323                    )
324                }
325            }
326
327            // All other shapes (primitives, objects, arrays, etc.) are neither
328            // null nor None, so ? has no effect.
329            _ => self.clone(),
330        };
331
332        self.propagate_names_to_child(child_shape.with_locations(locations.iter()), |name| {
333            name.question(locations.clone())
334        })
335    }
336
337    /// Returns a new [`Shape`] representing the input shape with any
338    /// possibility of `None` removed, but otherwise unchanged. This models the
339    /// behavior of a hypothetical `!` non-`None` assertion operator. When a
340    /// [`ShapeCase::Name`] shape reference has a `!` step in its subpath, that
341    /// `!` step can be applied to the shape when/if the named shape is later
342    /// declared/resolved, so the effect of the `!` is not lost.
343    #[must_use]
344    pub fn not_none(&self, locations: impl IntoIterator<Item = Location>) -> Shape {
345        let locations = locations.into_iter().collect::<Vec<_>>();
346
347        let child_shape = match self.case() {
348            ShapeCase::None => Shape::never(locations.clone()),
349
350            ShapeCase::One(shapes) => Shape::one(
351                shapes.iter().map(|shape| shape.not_none(locations.clone())),
352                locations.clone(),
353            ),
354
355            ShapeCase::All(shapes) => Shape::all(
356                shapes.iter().map(|shape| shape.not_none(locations.clone())),
357                locations.clone(),
358            ),
359
360            ShapeCase::Name(name, weak) => {
361                if let Some(named_shape) = weak.upgrade(name) {
362                    named_shape.not_none(locations.clone())
363                } else {
364                    Shape::new(
365                        ShapeCase::Name(name.not_none(locations.clone()), weak.clone()),
366                        locations.clone(),
367                    )
368                }
369            }
370
371            // All other shapes (primitives, objects, arrays, etc.) are not None,
372            // so ! has no effect.
373            _ => self.clone(),
374        };
375
376        self.propagate_names_to_child(child_shape.with_locations(locations.iter()), |name| {
377            name.not_none(locations.clone())
378        })
379    }
380
381    /// Applies an arbitrary [`NameCase`] to the current shape, returning a new
382    /// child shape. Any parent [`Name`]s of the [`NameCase`] are ignored, and
383    /// the resulting shape does not inherit any name(s) from the [`NameCase`].
384    #[must_use]
385    pub(crate) fn apply_name_case(
386        &self,
387        case: &NameCase,
388        locations: impl IntoIterator<Item = Location>,
389    ) -> Self {
390        match case {
391            NameCase::Base(name) => Self::name(name, locations),
392            NameCase::Field(_, name) => self.field(name, locations),
393            NameCase::AnyField(_) => self.any_field(locations),
394            NameCase::Item(_, index) => self.item(*index, locations),
395            NameCase::AnyItem(_) => self.any_item(locations),
396            NameCase::Question(_) => self.question(locations),
397            NameCase::NotNone(_) => self.not_none(locations),
398        }
399    }
400
401    #[must_use]
402    pub fn apply_name(&self, name: &Name) -> Self {
403        self.apply_name_case(name.case(), name.locs().cloned())
404    }
405}