shape/
child_shape.rs

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