Skip to main content

apollo_router/
json_ext.rs

1//! Performance oriented JSON manipulation.
2
3#![allow(missing_docs)] // FIXME
4
5use std::cmp::min;
6use std::fmt;
7
8use num_traits::ToPrimitive;
9use once_cell::sync::Lazy;
10use regex::Captures;
11use regex::Regex;
12use serde::Deserialize;
13use serde::Serialize;
14use serde_json_bytes::ByteString;
15use serde_json_bytes::Entry;
16use serde_json_bytes::Map;
17pub(crate) use serde_json_bytes::Value;
18
19use crate::error::FetchError;
20use crate::spec::Schema;
21use crate::spec::TYPENAME;
22
23/// A JSON object.
24pub(crate) type Object = Map<ByteString, Value>;
25
26const FRAGMENT_PREFIX: &str = "... on ";
27
28static TYPE_CONDITIONS_REGEX: Lazy<Regex> = Lazy::new(|| {
29    Regex::new(r"\|\[(?<condition>.+?)?\]")
30        .expect("this regex to check for type conditions is valid")
31});
32
33/// Extract the condition list from the regex captures.
34fn extract_matched_conditions(caps: &Captures) -> TypeConditions {
35    caps.name("condition")
36        .map(|c| c.as_str().split(',').map(|s| s.to_string()).collect())
37        .unwrap_or_default()
38}
39
40fn split_path_element_and_type_conditions(s: &str) -> (String, Option<TypeConditions>) {
41    let mut type_conditions = None;
42    let path_element = TYPE_CONDITIONS_REGEX.replace(s, |caps: &Captures| {
43        type_conditions = Some(extract_matched_conditions(caps));
44        ""
45    });
46    (path_element.to_string(), type_conditions)
47}
48
49macro_rules! extract_key_value_from_object {
50    ($object:expr, $key:literal, $pattern:pat => $var:ident) => {{
51        match $object.remove($key) {
52            Some($pattern) => Ok(Some($var)),
53            None | Some(crate::json_ext::Value::Null) => Ok(None),
54            _ => Err(concat!("invalid type for key: ", $key)),
55        }
56    }};
57    ($object:expr, $key:literal) => {{
58        match $object.remove($key) {
59            None | Some(crate::json_ext::Value::Null) => None,
60            Some(value) => Some(value),
61        }
62    }};
63}
64
65macro_rules! ensure_array {
66    ($value:expr) => {{
67        match $value {
68            crate::json_ext::Value::Array(a) => Ok(a),
69            _ => Err("invalid type, expected an array"),
70        }
71    }};
72}
73
74macro_rules! ensure_object {
75    ($value:expr) => {{
76        match $value {
77            crate::json_ext::Value::Object(o) => Ok(o),
78            _ => Err("invalid type, expected an object"),
79        }
80    }};
81}
82
83#[doc(hidden)]
84/// Extension trait for [`serde_json::Value`].
85pub(crate) trait ValueExt {
86    /// Deep merge the JSON objects, array and override the values in `&mut self` if they already
87    /// exists.
88    #[track_caller]
89    fn deep_merge(&mut self, other: Self);
90
91    /// Deep merge two JSON objects, overwriting values in `self` if it has the same key as `other`.
92    /// For GraphQL response objects, this uses schema information to avoid overwriting a concrete
93    /// `__typename` with an interface name.
94    #[track_caller]
95    fn type_aware_deep_merge(&mut self, other: Self, schema: &Schema);
96
97    /// Returns `true` if the values are equal and the objects are ordered the same.
98    ///
99    /// **Note:** this is recursive.
100    #[cfg(test)]
101    fn eq_and_ordered(&self, other: &Self) -> bool;
102
103    /// Returns `true` if the set is a subset of another, i.e., `other` contains at least all the
104    /// values in `self`.
105    #[track_caller]
106    #[cfg(test)]
107    fn is_subset(&self, superset: &Value) -> bool;
108
109    /// Create a `Value` by inserting a value at a subpath.
110    ///
111    /// This will create objects, arrays and null nodes as needed if they
112    /// are not present: the resulting Value is meant to be merged with an
113    /// existing one that contains those nodes.
114    #[track_caller]
115    fn from_path(path: &Path, value: Value) -> Value;
116
117    /// Insert a `Value` at a `Path`
118    #[track_caller]
119    fn insert(&mut self, path: &Path, value: Value) -> Result<(), FetchError>;
120
121    /// Get a `Value` from a `Path`
122    #[track_caller]
123    fn get_path<'a>(&'a self, schema: &Schema, path: &'a Path) -> Result<&'a Value, FetchError>;
124
125    /// Select all values matching a `Path`.
126    ///
127    /// the function passed as argument will be called with the values found and their Path
128    /// if it encounters an invalid value, it will ignore it and continue
129    #[track_caller]
130    fn select_values_and_paths<'a, F>(&'a self, schema: &Schema, path: &'a Path, f: F)
131    where
132        F: FnMut(&Path, &'a Value);
133
134    /// Select all values matching a `Path`, and allows to mutate those values.
135    ///
136    /// The behavior of the method is otherwise the same as it's non-mutable counterpart
137    #[track_caller]
138    fn select_values_and_paths_mut<'a, F>(&'a mut self, schema: &Schema, path: &'a Path, f: F)
139    where
140        F: FnMut(&Path, &'a mut Value);
141
142    #[track_caller]
143    fn is_valid_float_input(&self) -> bool;
144
145    #[track_caller]
146    fn is_valid_int_input(&self) -> bool;
147
148    #[track_caller]
149    fn is_valid_id_input(&self) -> bool;
150
151    /// Returns whether this value is an object that matches the provided type.
152    ///
153    /// More precisely, this checks that this value is an object, looks at
154    /// its `__typename` field and checks if that  `__typename` is either
155    /// `maybe_type` or a subtype of it.
156    ///
157    /// If the value is not an object, this will always return `false`, but
158    /// if the value is an object with no `__typename` field, then we default
159    /// to `true` (meaning that the absences of way to check, we assume the
160    /// value is of your expected type).
161    ///
162    /// TODO: in theory, this later default behaviour shouldn't matter since
163    /// we should avoid calling this in cases where the `__typename` is
164    /// unknown, but it is currently *relied* on due to some not-quite-right
165    /// behaviour. See the comment in `ExecutionService.call` around the call
166    /// to `select_values_and_paths` for details (the later relies on this
167    /// function to handle `PathElement::Fragment`).
168    #[track_caller]
169    fn is_object_of_type(&self, schema: &Schema, maybe_type: &str) -> bool;
170
171    fn as_i32(&self) -> Option<i32>;
172}
173
174impl ValueExt for Value {
175    fn deep_merge(&mut self, other: Self) {
176        match (self, other) {
177            (Value::Object(a), Value::Object(b)) => {
178                for (key, value) in b.into_iter() {
179                    match a.entry(key) {
180                        Entry::Vacant(e) => {
181                            e.insert(value);
182                        }
183                        Entry::Occupied(e) => {
184                            e.into_mut().deep_merge(value);
185                        }
186                    }
187                }
188            }
189            (Value::Array(a), Value::Array(mut b)) => {
190                for (b_value, a_value) in b.drain(..min(a.len(), b.len())).zip(a.iter_mut()) {
191                    a_value.deep_merge(b_value);
192                }
193
194                a.extend(b);
195            }
196            (_, Value::Null) => {}
197            (Value::Object(_), Value::Array(_)) => {
198                failfast_debug!("trying to replace an object with an array");
199            }
200            (Value::Array(_), Value::Object(_)) => {
201                failfast_debug!("trying to replace an array with an object");
202            }
203            (a, b) => {
204                if b != Value::Null {
205                    *a = b;
206                }
207            }
208        }
209    }
210
211    fn type_aware_deep_merge(&mut self, other: Self, schema: &Schema) {
212        match (self, other) {
213            (Value::Object(a), Value::Object(b)) => {
214                for (key, value) in b.into_iter() {
215                    let k = key.clone();
216                    match a.entry(key) {
217                        Entry::Vacant(e) => {
218                            e.insert(value);
219                        }
220                        Entry::Occupied(e) => match (e.into_mut(), value) {
221                            (Value::String(type1), Value::String(type2))
222                                if k.as_str() == TYPENAME =>
223                            {
224                                // If type1 is a subtype of type2, we skip this overwrite to preserve the more specific `__typename`
225                                // in the response. Ideally, we could use `Schema::is_implementation`, but that looks to be buggy
226                                // and does not catch the problem we are trying to resolve.
227                                if !schema.is_subtype(type2.as_str(), type1.as_str()) {
228                                    *type1 = type2;
229                                }
230                            }
231                            (t, s) => t.type_aware_deep_merge(s, schema),
232                        },
233                    }
234                }
235            }
236            (Value::Array(a), Value::Array(mut b)) => {
237                for (b_value, a_value) in b.drain(..min(a.len(), b.len())).zip(a.iter_mut()) {
238                    a_value.type_aware_deep_merge(b_value, schema);
239                }
240
241                a.extend(b);
242            }
243            (_, Value::Null) => {}
244            (Value::Object(_), Value::Array(_)) => {
245                failfast_debug!("trying to replace an object with an array");
246            }
247            (Value::Array(_), Value::Object(_)) => {
248                failfast_debug!("trying to replace an array with an object");
249            }
250            (a, b) => {
251                if b != Value::Null {
252                    *a = b;
253                }
254            }
255        }
256    }
257
258    #[cfg(test)]
259    fn eq_and_ordered(&self, other: &Self) -> bool {
260        match (self, other) {
261            (Value::Object(a), Value::Object(b)) => {
262                let mut it_a = a.iter();
263                let mut it_b = b.iter();
264
265                loop {
266                    match (it_a.next(), it_b.next()) {
267                        (Some(_), None) | (None, Some(_)) => break false,
268                        (None, None) => break true,
269                        (Some((field_a, value_a)), Some((field_b, value_b)))
270                            if field_a == field_b && ValueExt::eq_and_ordered(value_a, value_b) =>
271                        {
272                            continue;
273                        }
274                        (Some(_), Some(_)) => break false,
275                    }
276                }
277            }
278            (Value::Array(a), Value::Array(b)) => {
279                let mut it_a = a.iter();
280                let mut it_b = b.iter();
281
282                loop {
283                    match (it_a.next(), it_b.next()) {
284                        (Some(_), None) | (None, Some(_)) => break false,
285                        (None, None) => break true,
286                        (Some(value_a), Some(value_b))
287                            if ValueExt::eq_and_ordered(value_a, value_b) =>
288                        {
289                            continue;
290                        }
291                        (Some(_), Some(_)) => break false,
292                    }
293                }
294            }
295            (a, b) => a == b,
296        }
297    }
298
299    #[cfg(test)]
300    fn is_subset(&self, superset: &Value) -> bool {
301        match (self, superset) {
302            (Value::Object(subset), Value::Object(superset)) => {
303                subset.iter().all(|(key, value)| {
304                    if let Some(other) = superset.get(key) {
305                        value.is_subset(other)
306                    } else {
307                        false
308                    }
309                })
310            }
311            (Value::Array(subset), Value::Array(superset)) => {
312                subset.len() == superset.len()
313                    && subset.iter().enumerate().all(|(index, value)| {
314                        if let Some(other) = superset.get(index) {
315                            value.is_subset(other)
316                        } else {
317                            false
318                        }
319                    })
320            }
321            (a, b) => a == b,
322        }
323    }
324
325    #[track_caller]
326    fn from_path(path: &Path, value: Value) -> Value {
327        let mut res_value = Value::default();
328        let mut current_node = &mut res_value;
329
330        for p in path.iter() {
331            match p {
332                // Type conditions don't matter here since we're just creating a default value.
333                PathElement::Flatten(_) => {
334                    return res_value;
335                }
336
337                &PathElement::Index(index) => match current_node {
338                    Value::Array(a) => {
339                        for _ in 0..index {
340                            a.push(Value::default());
341                        }
342                        a.push(Value::default());
343                        current_node = a
344                            .get_mut(index)
345                            .expect("we just created the value at that index");
346                    }
347                    Value::Null => {
348                        let mut a = Vec::new();
349                        for _ in 0..index {
350                            a.push(Value::default());
351                        }
352                        a.push(Value::default());
353
354                        *current_node = Value::Array(a);
355                        current_node = current_node
356                            .as_array_mut()
357                            .expect("current_node was just set to a Value::Array")
358                            .get_mut(index)
359                            .expect("we just created the value at that index");
360                    }
361                    other => unreachable!("unreachable node: {:?}", other),
362                },
363                // Type conditions don't matter here since we're just creating a default value.
364                PathElement::Key(k, _) => {
365                    let mut m = Map::new();
366                    m.insert(k.as_str(), Value::default());
367
368                    *current_node = Value::Object(m);
369                    current_node = current_node
370                        .as_object_mut()
371                        .expect("current_node was just set to a Value::Object")
372                        .get_mut(k.as_str())
373                        .expect("the value at that key was just inserted");
374                }
375                PathElement::Fragment(_) => {}
376            }
377        }
378
379        *current_node = value;
380        res_value
381    }
382
383    /// Insert a `Value` at a `Path`
384    #[track_caller]
385    fn insert(&mut self, path: &Path, mut value: Value) -> Result<(), FetchError> {
386        let mut current_node = self;
387
388        for p in path.iter() {
389            match p {
390                PathElement::Flatten(type_conditions) => {
391                    value = filter_type_conditions(value, type_conditions);
392                    if current_node.is_null() {
393                        let a = Vec::new();
394                        *current_node = Value::Array(a);
395                    } else if !current_node.is_array() {
396                        return Err(FetchError::ExecutionPathNotFound {
397                            reason: "expected an array".to_string(),
398                        });
399                    }
400                }
401
402                &PathElement::Index(index) => match current_node {
403                    Value::Array(a) => {
404                        // add more elements if the index is after the end
405                        for _ in a.len()..index + 1 {
406                            a.push(Value::default());
407                        }
408                        current_node = a
409                            .get_mut(index)
410                            .expect("we just created the value at that index");
411                    }
412                    Value::Null => {
413                        let mut a = Vec::new();
414                        for _ in 0..index + 1 {
415                            a.push(Value::default());
416                        }
417
418                        *current_node = Value::Array(a);
419                        current_node = current_node
420                            .as_array_mut()
421                            .expect("current_node was just set to a Value::Array")
422                            .get_mut(index)
423                            .expect("we just created the value at that index");
424                    }
425                    _other => {
426                        return Err(FetchError::ExecutionPathNotFound {
427                            reason: "expected an array".to_string(),
428                        });
429                    }
430                },
431                PathElement::Key(k, type_conditions) => {
432                    value = filter_type_conditions(value, type_conditions);
433                    match current_node {
434                        Value::Object(o) => {
435                            current_node = o
436                                .get_mut(k.as_str())
437                                .expect("the value at that key was just inserted");
438                        }
439                        Value::Null => {
440                            let mut m = Map::new();
441                            m.insert(k.as_str(), Value::default());
442
443                            *current_node = Value::Object(m);
444                            current_node = current_node
445                                .as_object_mut()
446                                .expect("current_node was just set to a Value::Object")
447                                .get_mut(k.as_str())
448                                .expect("the value at that key was just inserted");
449                        }
450                        _other => {
451                            return Err(FetchError::ExecutionPathNotFound {
452                                reason: "expected an object".to_string(),
453                            });
454                        }
455                    }
456                }
457                PathElement::Fragment(_) => {}
458            }
459        }
460
461        *current_node = value;
462        Ok(())
463    }
464
465    /// Get a `Value` from a `Path`
466    #[track_caller]
467    fn get_path<'a>(&'a self, schema: &Schema, path: &'a Path) -> Result<&'a Value, FetchError> {
468        let mut res = Err(FetchError::ExecutionPathNotFound {
469            reason: "value not found".to_string(),
470        });
471        iterate_path(
472            schema,
473            &mut Path::default(),
474            &path.0,
475            self,
476            &mut |_path, value| {
477                res = Ok(value);
478            },
479        );
480        res
481    }
482
483    #[track_caller]
484    fn select_values_and_paths<'a, F>(&'a self, schema: &Schema, path: &'a Path, mut f: F)
485    where
486        F: FnMut(&Path, &'a Value),
487    {
488        iterate_path(schema, &mut Path::default(), &path.0, self, &mut f)
489    }
490
491    #[track_caller]
492    fn select_values_and_paths_mut<'a, F>(&'a mut self, schema: &Schema, path: &'a Path, mut f: F)
493    where
494        F: FnMut(&Path, &'a mut Value),
495    {
496        iterate_path_mut(schema, &mut Path::default(), &path.0, self, &mut f)
497    }
498
499    #[track_caller]
500    fn is_valid_id_input(&self) -> bool {
501        // https://spec.graphql.org/October2021/#sec-ID.Input-Coercion
502        match self {
503            // Any string and integer values are accepted
504            Value::String(_) => true,
505            Value::Number(n) => n.is_i64() || n.is_u64(),
506            _ => false,
507        }
508    }
509
510    #[track_caller]
511    fn is_valid_float_input(&self) -> bool {
512        // https://spec.graphql.org/draft/#sec-Float.Input-Coercion
513        match self {
514            // When expected as an input type, both integer and float input values are accepted.
515            Value::Number(n) if n.is_f64() => true,
516            // Integer input values are coerced to Float by adding an empty fractional part, for example 1.0 for the integer input value 1.
517            Value::Number(n) => n.is_i64(),
518            // All other input values, including strings with numeric content, must raise a request error indicating an incorrect type.
519            _ => false,
520        }
521    }
522
523    #[track_caller]
524    fn is_valid_int_input(&self) -> bool {
525        // https://spec.graphql.org/June2018/#sec-Int
526        // The Int scalar type represents a signed 32‐bit numeric non‐fractional value.
527        // When expected as an input type, only integer input values are accepted.
528        // All other input values, including strings with numeric content, must raise a query error indicating an incorrect type.
529        self.as_i64().and_then(|x| i32::try_from(x).ok()).is_some()
530            || self.as_u64().and_then(|x| i32::try_from(x).ok()).is_some()
531    }
532
533    #[track_caller]
534    fn is_object_of_type(&self, schema: &Schema, maybe_type: &str) -> bool {
535        self.is_object()
536            && self
537                .get(TYPENAME)
538                .and_then(|v| v.as_str())
539                .is_none_or(|typename| {
540                    typename == maybe_type || schema.is_subtype(maybe_type, typename)
541                })
542    }
543
544    fn as_i32(&self) -> Option<i32> {
545        self.as_i64()?.to_i32()
546    }
547}
548
549fn filter_type_conditions(value: Value, type_conditions: &Option<TypeConditions>) -> Value {
550    if let Some(tc) = type_conditions {
551        match value {
552            Value::Object(ref o) => {
553                if let Some(Value::String(type_name)) = &o.get("__typename")
554                    && !tc.iter().any(|tc| tc.as_str() == type_name.as_str())
555                {
556                    return Value::Null;
557                }
558            }
559            Value::Array(v) => {
560                return Value::Array(
561                    v.into_iter()
562                        .map(|v| filter_type_conditions(v, type_conditions))
563                        .collect(),
564                );
565            }
566            _ => {}
567        }
568    }
569    value
570}
571
572fn iterate_path<'a, F>(
573    schema: &Schema,
574    parent: &mut Path,
575    path: &'a [PathElement],
576    data: &'a Value,
577    f: &mut F,
578) where
579    F: FnMut(&Path, &'a Value),
580{
581    match path.first() {
582        None => f(parent, data),
583        Some(PathElement::Flatten(type_conditions)) => {
584            if let Some(array) = data.as_array() {
585                for (i, value) in array.iter().enumerate() {
586                    if let Some(tc) = type_conditions {
587                        if !tc.is_empty() {
588                            if let Value::Object(o) = value
589                                && let Some(Value::String(type_name)) = o.get("__typename")
590                                && tc.iter().any(|tc| tc.as_str() == type_name.as_str())
591                            {
592                                parent.push(PathElement::Index(i));
593                                iterate_path(schema, parent, &path[1..], value, f);
594                                parent.pop();
595                            }
596
597                            if let Value::Array(array) = value {
598                                for (i, value) in array.iter().enumerate() {
599                                    if let Value::Object(o) = value
600                                        && let Some(Value::String(type_name)) = o.get("__typename")
601                                        && tc.iter().any(|tc| tc.as_str() == type_name.as_str())
602                                    {
603                                        parent.push(PathElement::Index(i));
604                                        iterate_path(schema, parent, &path[1..], value, f);
605                                        parent.pop();
606                                    }
607                                }
608                            }
609                        }
610                    } else {
611                        parent.push(PathElement::Index(i));
612                        iterate_path(schema, parent, &path[1..], value, f);
613                        parent.pop();
614                    }
615                }
616            }
617        }
618        Some(PathElement::Index(i)) => {
619            if let Value::Array(a) = data
620                && let Some(value) = a.get(*i)
621            {
622                parent.push(PathElement::Index(*i));
623                iterate_path(schema, parent, &path[1..], value, f);
624                parent.pop();
625            }
626        }
627        Some(PathElement::Key(k, type_conditions)) => {
628            if let Some(tc) = type_conditions {
629                if !tc.is_empty() {
630                    if let Value::Object(o) = data {
631                        if let Some(value) = o.get(k.as_str())
632                            && let Some(Value::String(type_name)) = value.get("__typename")
633                            && tc.iter().any(|tc| tc.as_str() == type_name.as_str())
634                        {
635                            parent.push(PathElement::Key(k.to_string(), None));
636                            iterate_path(schema, parent, &path[1..], value, f);
637                            parent.pop();
638                        }
639                    } else if let Value::Array(array) = data {
640                        for (i, value) in array.iter().enumerate() {
641                            if let Value::Object(o) = value
642                                && let Some(Value::String(type_name)) = o.get("__typename")
643                                && tc.iter().any(|tc| tc.as_str() == type_name.as_str())
644                            {
645                                parent.push(PathElement::Index(i));
646                                iterate_path(schema, parent, path, value, f);
647                                parent.pop();
648                            }
649                        }
650                    }
651                }
652            } else if let Value::Object(o) = data {
653                if let Some(value) = o.get(k.as_str()) {
654                    parent.push(PathElement::Key(k.to_string(), None));
655                    iterate_path(schema, parent, &path[1..], value, f);
656                    parent.pop();
657                }
658            } else if let Value::Array(array) = data {
659                for (i, value) in array.iter().enumerate() {
660                    parent.push(PathElement::Index(i));
661                    iterate_path(schema, parent, path, value, f);
662                    parent.pop();
663                }
664            }
665        }
666        Some(PathElement::Fragment(name)) => {
667            if data.is_object_of_type(schema, name) {
668                // Note that (not unlike `Flatten`) we do not include the fragment in the `parent`
669                // path, because we want that path to be a "pure" response path. Fragments in path
670                // are used to essentially create a type-based choice in a "selection" path, but
671                // `parent` is a direct path to a specific position in the value and do not need
672                // fragments.
673                iterate_path(schema, parent, &path[1..], data, f);
674            } else if let Value::Array(array) = data {
675                for (i, value) in array.iter().enumerate() {
676                    parent.push(PathElement::Index(i));
677                    iterate_path(schema, parent, path, value, f);
678                    parent.pop();
679                }
680            }
681        }
682    }
683}
684
685fn iterate_path_mut<'a, F>(
686    schema: &Schema,
687    parent: &mut Path,
688    path: &'a [PathElement],
689    data: &'a mut Value,
690    f: &mut F,
691) where
692    F: FnMut(&Path, &'a mut Value),
693{
694    match path.first() {
695        None => f(parent, data),
696        Some(PathElement::Flatten(type_conditions)) => {
697            if let Some(array) = data.as_array_mut() {
698                for (i, value) in array.iter_mut().enumerate() {
699                    if let Some(tc) = type_conditions {
700                        if !tc.is_empty()
701                            && let Value::Object(o) = value
702                            && let Some(Value::String(type_name)) = o.get("__typename")
703                            && tc.iter().any(|tc| tc.as_str() == type_name.as_str())
704                        {
705                            parent.push(PathElement::Index(i));
706                            iterate_path_mut(schema, parent, &path[1..], value, f);
707                            parent.pop();
708                        }
709                    } else {
710                        parent.push(PathElement::Index(i));
711                        iterate_path_mut(schema, parent, &path[1..], value, f);
712                        parent.pop();
713                    }
714                }
715            }
716        }
717        Some(PathElement::Index(i)) => {
718            if let Value::Array(a) = data
719                && let Some(value) = a.get_mut(*i)
720            {
721                parent.push(PathElement::Index(*i));
722                iterate_path_mut(schema, parent, &path[1..], value, f);
723                parent.pop();
724            }
725        }
726        Some(PathElement::Key(k, type_conditions)) => {
727            if let Some(tc) = type_conditions {
728                if !tc.is_empty() {
729                    if let Value::Object(o) = data {
730                        if let Some(value) = o.get_mut(k.as_str())
731                            && let Some(Value::String(type_name)) = value.get("__typename")
732                            && tc.iter().any(|tc| tc.as_str() == type_name.as_str())
733                        {
734                            parent.push(PathElement::Key(k.to_string(), None));
735                            iterate_path_mut(schema, parent, &path[1..], value, f);
736                            parent.pop();
737                        }
738                    } else if let Value::Array(array) = data {
739                        for (i, value) in array.iter_mut().enumerate() {
740                            if let Value::Object(o) = value
741                                && let Some(Value::String(type_name)) = o.get("__typename")
742                                && tc.iter().any(|tc| tc.as_str() == type_name.as_str())
743                            {
744                                parent.push(PathElement::Index(i));
745                                iterate_path_mut(schema, parent, path, value, f);
746                                parent.pop();
747                            }
748                        }
749                    }
750                }
751            } else if let Value::Object(o) = data {
752                if let Some(value) = o.get_mut(k.as_str()) {
753                    parent.push(PathElement::Key(k.to_string(), None));
754                    iterate_path_mut(schema, parent, &path[1..], value, f);
755                    parent.pop();
756                }
757            } else if let Value::Array(array) = data {
758                for (i, value) in array.iter_mut().enumerate() {
759                    parent.push(PathElement::Index(i));
760                    iterate_path_mut(schema, parent, path, value, f);
761                    parent.pop();
762                }
763            }
764        }
765        Some(PathElement::Fragment(name)) => {
766            if data.is_object_of_type(schema, name) {
767                // Note that (not unlike `Flatten`) we do not include the fragment in the `parent`
768                // path, because we want that path to be a "pure" response path. Fragments in path
769                // are used to essentially create a type-based choice in a "selection" path, but
770                // `parent` is a direct path to a specific position in the value and do not need
771                // fragments.
772                iterate_path_mut(schema, parent, &path[1..], data, f);
773            } else if let Value::Array(array) = data {
774                for (i, value) in array.iter_mut().enumerate() {
775                    parent.push(PathElement::Index(i));
776                    iterate_path_mut(schema, parent, path, value, f);
777                    parent.pop();
778                }
779            }
780        }
781    }
782}
783
784/// A GraphQL path element that is composes of strings or numbers.
785/// e.g `/book/3/name`
786#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
787#[serde(untagged)]
788pub enum PathElement {
789    /// A path element that given an array will flatmap the content.
790    #[serde(
791        deserialize_with = "deserialize_flatten",
792        serialize_with = "serialize_flatten"
793    )]
794    Flatten(Option<TypeConditions>),
795
796    /// An index path element.
797    Index(usize),
798
799    /// A fragment application
800    #[serde(
801        deserialize_with = "deserialize_fragment",
802        serialize_with = "serialize_fragment"
803    )]
804    Fragment(String),
805
806    /// A key path element.
807    #[serde(deserialize_with = "deserialize_key", serialize_with = "serialize_key")]
808    Key(String, Option<TypeConditions>),
809}
810
811type TypeConditions = Vec<String>;
812
813#[derive(Clone, Debug, Eq, PartialEq)]
814pub enum ResponsePathElement<'a> {
815    /// An index path element.
816    Index(usize),
817
818    /// A key path element.
819    Key(&'a str),
820}
821
822fn deserialize_flatten<'de, D>(deserializer: D) -> Result<Option<TypeConditions>, D::Error>
823where
824    D: serde::Deserializer<'de>,
825{
826    deserializer.deserialize_str(FlattenVisitor)
827}
828
829struct FlattenVisitor;
830
831impl serde::de::Visitor<'_> for FlattenVisitor {
832    type Value = Option<TypeConditions>;
833
834    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
835        write!(
836            formatter,
837            "a string that is '@', potentially followed by type conditions"
838        )
839    }
840
841    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
842    where
843        E: serde::de::Error,
844    {
845        let (path_element, type_conditions) = split_path_element_and_type_conditions(s);
846        if path_element == "@" {
847            Ok(type_conditions)
848        } else {
849            Err(serde::de::Error::invalid_value(
850                serde::de::Unexpected::Str(s),
851                &self,
852            ))
853        }
854    }
855}
856
857fn serialize_flatten<S>(
858    type_conditions: &Option<TypeConditions>,
859    serializer: S,
860) -> Result<S::Ok, S::Error>
861where
862    S: serde::Serializer,
863{
864    let tc_string = if let Some(c) = type_conditions {
865        format!("|[{}]", c.join(","))
866    } else {
867        "".to_string()
868    };
869    let res = format!("@{tc_string}");
870    serializer.serialize_str(res.as_str())
871}
872
873fn deserialize_key<'de, D>(deserializer: D) -> Result<(String, Option<TypeConditions>), D::Error>
874where
875    D: serde::Deserializer<'de>,
876{
877    deserializer.deserialize_str(KeyVisitor)
878}
879
880struct KeyVisitor;
881
882impl serde::de::Visitor<'_> for KeyVisitor {
883    type Value = (String, Option<TypeConditions>);
884
885    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
886        write!(
887            formatter,
888            "a string, potentially followed by type conditions"
889        )
890    }
891
892    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
893    where
894        E: serde::de::Error,
895    {
896        Ok(split_path_element_and_type_conditions(s))
897    }
898}
899
900fn serialize_key<S>(
901    key: &String,
902    type_conditions: &Option<TypeConditions>,
903    serializer: S,
904) -> Result<S::Ok, S::Error>
905where
906    S: serde::Serializer,
907{
908    let tc_string = if let Some(c) = type_conditions {
909        format!("|[{}]", c.join(","))
910    } else {
911        "".to_string()
912    };
913    let res = format!("{key}{tc_string}");
914    serializer.serialize_str(res.as_str())
915}
916
917fn deserialize_fragment<'de, D>(deserializer: D) -> Result<String, D::Error>
918where
919    D: serde::Deserializer<'de>,
920{
921    deserializer.deserialize_str(FragmentVisitor)
922}
923
924struct FragmentVisitor;
925
926impl serde::de::Visitor<'_> for FragmentVisitor {
927    type Value = String;
928
929    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
930        write!(formatter, "a string that begins with '... on '")
931    }
932
933    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
934    where
935        E: serde::de::Error,
936    {
937        s.strip_prefix(FRAGMENT_PREFIX)
938            .map(|v| v.to_string())
939            .ok_or_else(|| serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self))
940    }
941}
942
943fn serialize_fragment<S>(name: &String, serializer: S) -> Result<S::Ok, S::Error>
944where
945    S: serde::Serializer,
946{
947    serializer.serialize_str(format!("{FRAGMENT_PREFIX}{name}").as_str())
948}
949
950fn flatten_from_str(s: &str) -> Result<PathElement, String> {
951    let (path_element, type_conditions) = split_path_element_and_type_conditions(s);
952    if path_element != "@" {
953        return Err("invalid flatten".to_string());
954    }
955    Ok(PathElement::Flatten(type_conditions))
956}
957
958fn key_from_str(s: &str) -> Result<PathElement, String> {
959    let (key, type_conditions) = split_path_element_and_type_conditions(s);
960    Ok(PathElement::Key(key, type_conditions))
961}
962
963/// A path into the result document.
964///
965/// This can be composed of strings and numbers
966#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Default, Hash)]
967#[serde(transparent)]
968pub struct Path(pub Vec<PathElement>);
969
970impl Path {
971    pub fn from_slice<T: AsRef<str>>(s: &[T]) -> Self {
972        Self(
973            s.iter()
974                .map(|x| x.as_ref())
975                .map(|s| {
976                    if let Ok(index) = s.parse::<usize>() {
977                        PathElement::Index(index)
978                    } else if s.contains('@') {
979                        flatten_from_str(s).unwrap_or(PathElement::Flatten(None))
980                    } else {
981                        s.strip_prefix(FRAGMENT_PREFIX).map_or_else(
982                            || key_from_str(s).unwrap_or(PathElement::Key(s.to_string(), None)),
983                            |name| PathElement::Fragment(name.to_string()),
984                        )
985                    }
986                })
987                .collect(),
988        )
989    }
990
991    pub fn from_response_slice(s: &[ResponsePathElement]) -> Self {
992        Self(
993            s.iter()
994                .map(|x| match x {
995                    ResponsePathElement::Index(index) => PathElement::Index(*index),
996                    ResponsePathElement::Key(s) => PathElement::Key(s.to_string(), None),
997                })
998                .collect(),
999        )
1000    }
1001
1002    pub fn iter(&self) -> impl Iterator<Item = &PathElement> {
1003        self.0.iter()
1004    }
1005
1006    pub fn is_empty(&self) -> bool {
1007        self.0.is_empty()
1008    }
1009
1010    pub fn len(&self) -> usize {
1011        self.0.len()
1012    }
1013
1014    pub fn empty() -> Path {
1015        Path(Default::default())
1016    }
1017
1018    pub fn parent(&self) -> Option<Path> {
1019        if self.is_empty() {
1020            None
1021        } else {
1022            Some(Path(self.iter().take(self.len() - 1).cloned().collect()))
1023        }
1024    }
1025
1026    pub fn join(&self, other: impl AsRef<Self>) -> Self {
1027        let other = other.as_ref();
1028        let mut new = Vec::with_capacity(self.len() + other.len());
1029        new.extend(self.iter().cloned());
1030        new.extend(other.iter().cloned());
1031        Path(new)
1032    }
1033
1034    pub fn push(&mut self, element: PathElement) {
1035        self.0.push(element)
1036    }
1037
1038    pub fn pop(&mut self) -> Option<PathElement> {
1039        self.0.pop()
1040    }
1041
1042    pub fn last(&self) -> Option<&PathElement> {
1043        self.0.last()
1044    }
1045
1046    pub fn last_key(&mut self) -> Option<String> {
1047        self.0.last().and_then(|elem| match elem {
1048            PathElement::Key(key, type_conditions) => {
1049                let mut tc = String::new();
1050                if let Some(c) = type_conditions {
1051                    tc = format!("|[{}]", c.join(","));
1052                };
1053                Some(format!("{key}{tc}"))
1054            }
1055            _ => None,
1056        })
1057    }
1058
1059    pub fn starts_with(&self, other: &Path) -> bool {
1060        self.0.starts_with(&other.0[..])
1061    }
1062
1063    // Removes the empty key if at root (used for TypedConditions)
1064    pub fn remove_empty_key_root(&self) -> Self {
1065        if let Some(PathElement::Key(k, type_conditions)) = self.0.first()
1066            && k.is_empty()
1067            && type_conditions.is_none()
1068        {
1069            return Path(self.iter().skip(1).cloned().collect());
1070        }
1071
1072        self.clone()
1073    }
1074
1075    // Checks whether self and other are equal if PathElement::Flatten and PathElement::Index are
1076    // treated as equal, and PathElement::Key type conditions are ignored.
1077    //
1078    // Type conditions on a `Key` only matter while a path is still being matched against live,
1079    // still-typed data (see `iterate_path`, which strips them once matched: they're a filter
1080    // applied once, not part of a materialized path's identity). By the time we're comparing an
1081    // error's declared path (which can still carry a type condition from the query plan) against
1082    // the real per-entity paths a fetch actually produced, every candidate path already satisfies
1083    // whatever type condition applied, so a `Key` with a type condition and a `Key` without one at
1084    // the same position denote the same location and must compare equal here.
1085    pub fn equal_if_flattened(&self, other: &Path) -> bool {
1086        if self.len() != other.len() {
1087            return false;
1088        }
1089
1090        for (elem1, elem2) in self.iter().zip(other.iter()) {
1091            let equal_elements = match (elem1, elem2) {
1092                (PathElement::Index(_), PathElement::Flatten(_)) => true,
1093                (PathElement::Flatten(_), PathElement::Index(_)) => true,
1094                (PathElement::Key(k1, _), PathElement::Key(k2, _)) => k1 == k2,
1095                (elem1, elem2) => elem1 == elem2,
1096            };
1097            if !equal_elements {
1098                return false;
1099            }
1100        }
1101
1102        true
1103    }
1104}
1105
1106impl FromIterator<PathElement> for Path {
1107    fn from_iter<T: IntoIterator<Item = PathElement>>(iter: T) -> Self {
1108        Path(iter.into_iter().collect())
1109    }
1110}
1111
1112impl AsRef<Path> for Path {
1113    fn as_ref(&self) -> &Path {
1114        self
1115    }
1116}
1117
1118impl<T> From<T> for Path
1119where
1120    T: AsRef<str>,
1121{
1122    fn from(s: T) -> Self {
1123        Self(
1124            s.as_ref()
1125                .split('/')
1126                .map(|s| {
1127                    if let Ok(index) = s.parse::<usize>() {
1128                        PathElement::Index(index)
1129                    } else if s.contains('@') {
1130                        flatten_from_str(s).unwrap()
1131                    } else {
1132                        s.strip_prefix(FRAGMENT_PREFIX).map_or_else(
1133                            || key_from_str(s).unwrap_or(PathElement::Key(s.to_string(), None)),
1134                            |name| PathElement::Fragment(name.to_string()),
1135                        )
1136                    }
1137                })
1138                .collect(),
1139        )
1140    }
1141}
1142
1143impl fmt::Display for Path {
1144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1145        for element in self.iter() {
1146            write!(f, "/")?;
1147            match element {
1148                PathElement::Index(index) => write!(f, "{index}")?,
1149                PathElement::Key(key, type_conditions) => {
1150                    write!(f, "{key}")?;
1151                    if let Some(c) = type_conditions {
1152                        write!(f, "|[{}]", c.join(","))?;
1153                    };
1154                }
1155                PathElement::Flatten(type_conditions) => {
1156                    write!(f, "@")?;
1157                    if let Some(c) = type_conditions {
1158                        write!(f, "|[{}]", c.join(","))?;
1159                    };
1160                }
1161                PathElement::Fragment(name) => {
1162                    write!(f, "{FRAGMENT_PREFIX}{name}")?;
1163                }
1164            }
1165        }
1166        Ok(())
1167    }
1168}
1169
1170#[cfg(test)]
1171mod tests {
1172    use serde_json_bytes::json;
1173
1174    use super::*;
1175
1176    macro_rules! assert_is_subset {
1177        ($a:expr, $b:expr $(,)?) => {
1178            assert!($a.is_subset(&$b));
1179        };
1180    }
1181
1182    macro_rules! assert_is_not_subset {
1183        ($a:expr, $b:expr $(,)?) => {
1184            assert!(!$a.is_subset(&$b));
1185        };
1186    }
1187
1188    /// Functions that walk on path needs a schema to handle potential fragment (type conditions) in
1189    /// the path, and so we use the following simple schema for tests. Note however that tests that
1190    /// don't use fragments in the path essentially ignore this schema.
1191    fn test_schema() -> Schema {
1192        Schema::parse(
1193            r#"
1194           schema
1195             @link(url: "https://specs.apollo.dev/link/v1.0")
1196             @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
1197           {
1198             query: Query
1199           }
1200
1201           directive @join__graph(name: String!, url: String!) on ENUM_VALUE
1202           directive @link( url: String as: String for: link__Purpose import: [link__Import]) repeatable on SCHEMA
1203           scalar link__Import
1204
1205           enum join__Graph {
1206             FAKE @join__graph(name:"fake" url: "http://localhost:4001/fake")
1207           }
1208
1209           enum link__Purpose {
1210             SECURITY
1211             EXECUTION
1212           }
1213
1214           type Query {
1215             i: [I]
1216           }
1217
1218           interface I {
1219             x: Int
1220           }
1221
1222           type A implements I {
1223             x: Int
1224           }
1225
1226           type B {
1227             y: Int
1228           }
1229        "#,
1230            &Default::default(),
1231        )
1232        .unwrap()
1233    }
1234
1235    fn select_values<'a>(
1236        schema: &Schema,
1237        path: &'a Path,
1238        data: &'a Value,
1239    ) -> Result<Vec<&'a Value>, FetchError> {
1240        let mut v = Vec::new();
1241        data.select_values_and_paths(schema, path, |_path, value| {
1242            v.push(value);
1243        });
1244        Ok(v)
1245    }
1246
1247    #[test]
1248    fn test_get_at_path() {
1249        let schema = test_schema();
1250        let json = json!({"obj":{"arr":[{"prop1":1},{"prop1":2}]}});
1251        let path = Path::from("obj/arr/1/prop1");
1252        let result = select_values(&schema, &path, &json).unwrap();
1253        assert_eq!(result, vec![&Value::Number(2.into())]);
1254    }
1255
1256    #[test]
1257    fn test_get_at_path_flatmap() {
1258        let schema = test_schema();
1259        let json = json!({"obj":{"arr":[{"prop1":1},{"prop1":2}]}});
1260        let path = Path::from("obj/arr/@");
1261        let result = select_values(&schema, &path, &json).unwrap();
1262        assert_eq!(result, vec![&json!({"prop1":1}), &json!({"prop1":2})]);
1263    }
1264
1265    #[test]
1266    fn test_get_at_path_flatmap_nested() {
1267        let schema = test_schema();
1268        let json = json!({
1269            "obj": {
1270                "arr": [
1271                    {
1272                        "prop1": [
1273                            {"prop2": {"prop3": 1}, "prop4": -1},
1274                            {"prop2": {"prop3": 2}, "prop4": -2},
1275                        ],
1276                    },
1277                    {
1278                        "prop1": [
1279                            {"prop2": {"prop3": 3}, "prop4": -3},
1280                            {"prop2": {"prop3": 4}, "prop4": -4},
1281                        ],
1282                    },
1283                ],
1284            },
1285        });
1286        let path = Path::from("obj/arr/@/prop1/@/prop2");
1287        let result = select_values(&schema, &path, &json).unwrap();
1288        assert_eq!(
1289            result,
1290            vec![
1291                &json!({"prop3":1}),
1292                &json!({"prop3":2}),
1293                &json!({"prop3":3}),
1294                &json!({"prop3":4}),
1295            ],
1296        );
1297    }
1298
1299    #[test]
1300    fn test_deep_merge() {
1301        let mut json = json!({"obj":{"arr":[{"prop1":1},{"prop2":2}]}});
1302        json.deep_merge(json!({"obj":{"arr":[{"prop1":2,"prop3":3},{"prop4":4}]}}));
1303        assert_eq!(
1304            json,
1305            json!({"obj":{"arr":[{"prop1":2, "prop3":3},{"prop2":2, "prop4":4}]}})
1306        );
1307    }
1308
1309    #[test]
1310    fn interface_typename_merging() {
1311        let schema = Schema::parse(
1312                r#"
1313            schema
1314                @link(url: "https://specs.apollo.dev/link/v1.0")
1315                @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
1316            {
1317                query: Query
1318            }
1319            directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA
1320            directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
1321            directive @join__graph(name: String!, url: String!) on ENUM_VALUE
1322
1323            scalar link__Import
1324            scalar join__FieldSet
1325
1326            enum link__Purpose {
1327                SECURITY
1328                EXECUTION
1329            }
1330
1331            enum join__Graph {
1332                TEST @join__graph(name: "test", url: "http://localhost:4001/graphql")
1333            }
1334
1335            interface I {
1336                s: String
1337            }
1338
1339            type C implements I {
1340                s: String
1341            }
1342
1343            type Query {
1344                i: I
1345            }
1346        "#,
1347            &Default::default(),
1348        )
1349        .expect("valid schema");
1350        let mut response1 = json!({
1351            "__typename": "C"
1352        });
1353        let response2 = json!({
1354            "__typename": "I",
1355            "s": "data"
1356        });
1357
1358        response1.type_aware_deep_merge(response2, &schema);
1359
1360        assert_eq!(
1361            response1,
1362            json!({
1363                "__typename": "C",
1364                "s": "data"
1365            })
1366        );
1367    }
1368
1369    #[test]
1370    fn test_is_subset_eq() {
1371        assert_is_subset!(
1372            json!({"obj":{"arr":[{"prop1":1},{"prop4":4}]}}),
1373            json!({"obj":{"arr":[{"prop1":1},{"prop4":4}]}}),
1374        );
1375    }
1376
1377    #[test]
1378    fn test_is_subset_missing_pop() {
1379        assert_is_subset!(
1380            json!({"obj":{"arr":[{"prop1":1},{"prop4":4}]}}),
1381            json!({"obj":{"arr":[{"prop1":1,"prop3":3},{"prop4":4}]}}),
1382        );
1383    }
1384
1385    #[test]
1386    fn test_is_subset_array_lengths_differ() {
1387        assert_is_not_subset!(
1388            json!({"obj":{"arr":[{"prop1":1}]}}),
1389            json!({"obj":{"arr":[{"prop1":1,"prop3":3},{"prop4":4}]}}),
1390        );
1391    }
1392
1393    #[test]
1394    fn test_is_subset_extra_prop() {
1395        assert_is_not_subset!(
1396            json!({"obj":{"arr":[{"prop1":1,"prop3":3},{"prop4":4}]}}),
1397            json!({"obj":{"arr":[{"prop1":1},{"prop4":4}]}}),
1398        );
1399    }
1400
1401    #[test]
1402    fn eq_and_ordered() {
1403        // test not objects
1404        assert!(json!([1, 2, 3]).eq_and_ordered(&json!([1, 2, 3])));
1405        assert!(!json!([1, 3, 2]).eq_and_ordered(&json!([1, 2, 3])));
1406
1407        // test objects not nested
1408        assert!(json!({"foo":1,"bar":2}).eq_and_ordered(&json!({"foo":1,"bar":2})));
1409        assert!(!json!({"foo":1,"bar":2}).eq_and_ordered(&json!({"foo":1,"bar":3})));
1410        assert!(!json!({"foo":1,"bar":2}).eq_and_ordered(&json!({"foo":1,"bar":2,"baz":3})));
1411        assert!(!json!({"foo":1,"bar":2,"baz":3}).eq_and_ordered(&json!({"foo":1,"bar":2})));
1412        assert!(!json!({"bar":2,"foo":1}).eq_and_ordered(&json!({"foo":1,"bar":2})));
1413
1414        // test objects nested
1415        assert!(json!({"baz":{"foo":1,"bar":2}}).eq_and_ordered(&json!({"baz":{"foo":1,"bar":2}})));
1416        assert!(
1417            !json!({"baz":{"bar":2,"foo":1}}).eq_and_ordered(&json!({"baz":{"foo":1,"bar":2}}))
1418        );
1419        assert!(!json!([1,{"bar":2,"foo":1},2]).eq_and_ordered(&json!([1,{"foo":1,"bar":2},2])));
1420    }
1421
1422    #[test]
1423    fn test_from_path() {
1424        let json = json!([{"prop1":1},{"prop1":2}]);
1425        let path = Path::from("obj/arr");
1426        let result = Value::from_path(&path, json);
1427        assert_eq!(result, json!({"obj":{"arr":[{"prop1":1},{"prop1":2}]}}));
1428    }
1429
1430    #[test]
1431    fn test_from_path_index() {
1432        let json = json!({"prop1":1});
1433        let path = Path::from("obj/arr/1");
1434        let result = Value::from_path(&path, json);
1435        assert_eq!(result, json!({"obj":{"arr":[null, {"prop1":1}]}}));
1436    }
1437
1438    #[test]
1439    fn test_from_path_flatten() {
1440        let json = json!({"prop1":1});
1441        let path = Path::from("obj/arr/@/obj2");
1442        let result = Value::from_path(&path, json);
1443        assert_eq!(result, json!({"obj":{"arr":null}}));
1444    }
1445
1446    #[test]
1447    fn test_equal_if_flattened_ignores_key_type_conditions() {
1448        // A fetch-level error's declared path (from the query plan) can retain a
1449        // type condition on a `Key` element when the fetch is reached through an abstract-typed
1450        // (interface/union) field, e.g. `.../node|[BlockBuilderCoreListingCollection]/collection`.
1451        // The real per-entity paths produced during execution never carry that type condition
1452        // (`iterate_path` strips it once matched). These must still compare equal, or a fetch-level
1453        // error (e.g. a subgraph timeout) silently fails to attach to any entity and is dropped.
1454        let declared_error_path =
1455            Path::from("edges/@/node|[BlockBuilderCoreListingCollection]/collection");
1456        let real_entity_path = Path::from("edges/3/node/collection");
1457        assert!(declared_error_path.equal_if_flattened(&real_entity_path));
1458        assert!(real_entity_path.equal_if_flattened(&declared_error_path));
1459
1460        // Different key names must still not match, type condition or no.
1461        let different_key = Path::from("edges/3/node/otherField");
1462        assert!(!declared_error_path.equal_if_flattened(&different_key));
1463
1464        // Pre-existing Index/Flatten equivalence must still hold.
1465        let flatten_path = Path::from("edges/@/node/collection");
1466        let index_path = Path::from("edges/7/node/collection");
1467        assert!(flatten_path.equal_if_flattened(&index_path));
1468    }
1469
1470    #[test]
1471    fn test_is_object_of_type() {
1472        let schema = test_schema();
1473
1474        // Basic matching
1475        assert!(json!({ "__typename": "A", "x": "42"}).is_object_of_type(&schema, "A"));
1476
1477        // Matching with subtyping
1478        assert!(json!({ "__typename": "A", "x": "42"}).is_object_of_type(&schema, "I"));
1479
1480        // Matching when missing __typename (see comment on the method declaration).
1481        assert!(json!({ "x": "42"}).is_object_of_type(&schema, "A"));
1482
1483        // Non-matching because not an object
1484        assert!(!json!([{ "__typename": "A", "x": "42"}]).is_object_of_type(&schema, "A"));
1485        assert!(!json!("foo").is_object_of_type(&schema, "I"));
1486        assert!(!json!(42).is_object_of_type(&schema, "I"));
1487
1488        // Non-matching because not of the asked type.
1489        assert!(!json!({ "__typename": "B", "y": "42"}).is_object_of_type(&schema, "A"));
1490        assert!(!json!({ "__typename": "B", "y": "42"}).is_object_of_type(&schema, "I"));
1491    }
1492
1493    #[test]
1494    fn test_get_at_path_with_conditions() {
1495        let schema = test_schema();
1496        let json = json!({
1497            "i": [
1498                {
1499                    "__typename": "A",
1500                    "x": 0,
1501                },
1502                {
1503                    "__typename": "B",
1504                    "y": 1,
1505                },
1506                {
1507                    "__typename": "B",
1508                    "y": 2,
1509                },
1510                {
1511                    "__typename": "A",
1512                    "x": 3,
1513                },
1514            ],
1515        });
1516        let path = Path::from("i/... on A");
1517        let result = select_values(&schema, &path, &json).unwrap();
1518        assert_eq!(
1519            result,
1520            vec![
1521                &json!({
1522                    "__typename": "A",
1523                    "x": 0,
1524                }),
1525                &json!({
1526                    "__typename": "A",
1527                    "x": 3,
1528                }),
1529            ],
1530        );
1531    }
1532
1533    #[test]
1534    fn path_serde_json() {
1535        let path: Path = serde_json::from_str(
1536            r#"[
1537            "k",
1538            "... on T",
1539            "@",
1540            "arr",
1541            3
1542        ]"#,
1543        )
1544        .unwrap();
1545        assert_eq!(
1546            path.0,
1547            vec![
1548                PathElement::Key("k".to_string(), None),
1549                PathElement::Fragment("T".to_string()),
1550                PathElement::Flatten(None),
1551                PathElement::Key("arr".to_string(), None),
1552                PathElement::Index(3),
1553            ]
1554        );
1555
1556        assert_eq!(
1557            serde_json::to_string(&path).unwrap(),
1558            "[\"k\",\"... on T\",\"@\",\"arr\",3]",
1559        );
1560    }
1561}