Skip to main content

apollo_federation/connectors/json_selection/
selection_set.rs

1//! Functions for applying a [`SelectionSet`] to a [`JSONSelection`]. This creates a new
2//! `JSONSelection` mapping to the fields on the selection set, and excluding parts of the
3//! original `JSONSelection` that are not needed by the selection set.
4
5#![cfg_attr(
6    not(test),
7    deny(
8        clippy::exit,
9        clippy::panic,
10        clippy::unwrap_used,
11        clippy::expect_used,
12        clippy::indexing_slicing,
13        clippy::unimplemented,
14        clippy::todo,
15        missing_docs
16    )
17)]
18
19use apollo_compiler::ExecutableDocument;
20use apollo_compiler::Node;
21use apollo_compiler::collections::IndexSet;
22use apollo_compiler::executable::Field;
23use apollo_compiler::executable::FieldSet;
24use apollo_compiler::executable::Selection;
25use apollo_compiler::executable::SelectionSet;
26use multimap::MultiMap;
27
28use super::lit_expr::LitExpr;
29use super::location::Ranged;
30use super::location::WithRange;
31use super::parser::PathList;
32use crate::connectors::JSONSelection;
33use crate::connectors::PathSelection;
34use crate::connectors::SubSelection;
35use crate::connectors::json_selection::Alias;
36use crate::connectors::json_selection::NamedSelection;
37use crate::connectors::json_selection::NamingPrefix;
38use crate::connectors::json_selection::TopLevelSelection;
39
40impl JSONSelection {
41    /// Apply a selection set to create a new [`JSONSelection`]
42    ///
43    /// Operations from the query planner will never contain key fields (because
44    /// it already has them from a previous fetch) but we might need those
45    /// fields for things like sorting entities in a batch. If the optional
46    /// `required_keys` is provided, we'll merge those fields into the selection
47    /// set before applying it to the JSONSelection.
48    pub fn apply_selection_set(
49        &self,
50        abstract_types: &IndexSet<String>,
51        document: &ExecutableDocument,
52        selection_set: &SelectionSet,
53        required_keys: Option<&FieldSet>,
54    ) -> Self {
55        let selection_set = required_keys.map_or_else(
56            || selection_set.clone(),
57            |keys| {
58                keys.selection_set.selections.iter().cloned().fold(
59                    selection_set.clone(),
60                    |mut acc, selection| {
61                        acc.push(selection);
62                        acc
63                    },
64                )
65            },
66        );
67
68        match &self.inner {
69            TopLevelSelection::Named(sub) => Self {
70                inner: TopLevelSelection::Named(sub.apply_selection_set(
71                    abstract_types,
72                    document,
73                    &selection_set,
74                )),
75                spec: self.spec,
76            },
77            TopLevelSelection::Path(path) => Self {
78                inner: TopLevelSelection::Path(path.apply_selection_set(
79                    abstract_types,
80                    document,
81                    &selection_set,
82                )),
83                spec: self.spec,
84            },
85        }
86    }
87}
88
89impl SubSelection {
90    /// Apply a selection set to create a new [`SubSelection`]
91    pub fn apply_selection_set(
92        &self,
93        abstract_types: &IndexSet<String>,
94        document: &ExecutableDocument,
95        selection_set: &SelectionSet,
96    ) -> Self {
97        let mut new_selections = Vec::new();
98        let field_map = map_fields_by_name(document, selection_set);
99
100        // When the operation contains __typename, it might be used to complete
101        // an entity reference (e.g. `__typename id`) for a subsequent fetch.
102        if field_map.contains_key("__typename")
103            // Only inject __typename for non-abstract types because output JSON
104            // for abstract types must provide a concrete __typename, so there's
105            // nothing we can confidently inject here.
106            && !abstract_types.contains(&selection_set.ty.to_string())
107        {
108            // Since `_Entity` is an abstract type (union), we should never see
109            // it here. For reasons I (Lenny) don't understand, persisted
110            // queries may contain `__typename` for `_entities` queries. We
111            // never want to emit `__typename: "_Entity"`, so we'll guard
112            // against that case.
113            debug_assert_ne!(selection_set.ty.to_string(), "_Entity");
114
115            new_selections.push(NamedSelection {
116                prefix: NamingPrefix::Alias(Alias::new("__typename")),
117                path: PathSelection {
118                    path: WithRange::new(
119                        PathList::Expr(
120                            WithRange::new(LitExpr::String(selection_set.ty.to_string()), None),
121                            WithRange::new(PathList::Empty, None),
122                        ),
123                        None,
124                    ),
125                },
126            });
127        }
128
129        for selection in &self.selections {
130            if let Some(single_key_for_selection) = selection.get_single_key() {
131                // In the single-Key case, we can filter out any selections
132                // whose single key does not match anything in the field_map.
133                if let Some(fields) = field_map.get_vec(single_key_for_selection.as_str()) {
134                    for field in fields {
135                        let applied_path = selection.path.apply_selection_set(
136                            abstract_types,
137                            document,
138                            &field.selection_set,
139                        );
140
141                        new_selections.push(NamedSelection {
142                            prefix: selection.prefix.clone(),
143                            path: applied_path,
144                        });
145                    }
146                } else {
147                    // If the selection had a single output key and that key
148                    // does not appear in field_map, we can skip the selection.
149                }
150            } else {
151                // If the NamedSelection::Path does not have a single output key
152                // (has no alias and is not a single field selection), then it's
153                // tricky to know if we should prune the selection, so we
154                // conservatively preserve it, using a transformed path.
155                new_selections.push(NamedSelection {
156                    prefix: selection.prefix.clone(),
157                    path: selection.path.apply_selection_set(
158                        abstract_types,
159                        document,
160                        selection_set,
161                    ),
162                });
163            }
164        }
165
166        Self {
167            selections: new_selections,
168            // Keep the old range even though it may be inaccurate after the
169            // removal of selections, since it still indicates where the
170            // original SubSelection came from.
171            range: self.range.clone(),
172        }
173    }
174}
175
176impl PathSelection {
177    /// Apply a selection set to create a new [`PathSelection`]
178    pub fn apply_selection_set(
179        &self,
180        abstract_types: &IndexSet<String>,
181        document: &ExecutableDocument,
182        selection_set: &SelectionSet,
183    ) -> Self {
184        Self {
185            path: WithRange::new(
186                self.path
187                    .apply_selection_set(abstract_types, document, selection_set),
188                self.path.range(),
189            ),
190        }
191    }
192}
193
194impl PathList {
195    pub(crate) fn apply_selection_set(
196        &self,
197        abstract_types: &IndexSet<String>,
198        document: &ExecutableDocument,
199        selection_set: &SelectionSet,
200    ) -> Self {
201        match self {
202            Self::Var(name, path) => Self::Var(
203                name.clone(),
204                WithRange::new(
205                    path.apply_selection_set(abstract_types, document, selection_set),
206                    path.range(),
207                ),
208            ),
209            Self::Key(key, path) => Self::Key(
210                key.clone(),
211                WithRange::new(
212                    path.apply_selection_set(abstract_types, document, selection_set),
213                    path.range(),
214                ),
215            ),
216            Self::Expr(expr, path) => Self::Expr(
217                expr.clone(),
218                WithRange::new(
219                    path.apply_selection_set(abstract_types, document, selection_set),
220                    path.range(),
221                ),
222            ),
223            Self::Method(method_name, args, path) => Self::Method(
224                method_name.clone(),
225                args.clone(),
226                WithRange::new(
227                    path.apply_selection_set(abstract_types, document, selection_set),
228                    path.range(),
229                ),
230            ),
231            Self::Question(tail) => Self::Question(WithRange::new(
232                tail.apply_selection_set(abstract_types, document, selection_set),
233                tail.range(),
234            )),
235            Self::Selection(sub) => {
236                Self::Selection(sub.apply_selection_set(abstract_types, document, selection_set))
237            }
238            Self::Empty => Self::Empty,
239        }
240    }
241}
242
243fn map_fields_by_name<'a>(
244    document: &'a ExecutableDocument,
245    set: &'a SelectionSet,
246) -> MultiMap<String, &'a Node<Field>> {
247    let mut map = MultiMap::new();
248    map_fields_by_name_impl(document, set, &mut map);
249    map
250}
251
252fn map_fields_by_name_impl<'a>(
253    document: &'a ExecutableDocument,
254    set: &'a SelectionSet,
255    map: &mut MultiMap<String, &'a Node<Field>>,
256) {
257    for selection in &set.selections {
258        match selection {
259            Selection::Field(field) => {
260                map.insert(field.name.to_string(), field);
261            }
262            Selection::FragmentSpread(f) => {
263                if let Some(fragment) = f.fragment_def(document) {
264                    map_fields_by_name_impl(document, &fragment.selection_set, map);
265                }
266            }
267            Selection::InlineFragment(fragment) => {
268                map_fields_by_name_impl(document, &fragment.selection_set, map);
269            }
270        }
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use apollo_compiler::ExecutableDocument;
277    use apollo_compiler::Schema;
278    use apollo_compiler::collections::IndexSet;
279    use apollo_compiler::executable::FieldSet;
280    use apollo_compiler::executable::SelectionSet;
281    use apollo_compiler::name;
282    use apollo_compiler::validation::Valid;
283    use pretty_assertions::assert_eq;
284
285    use crate::assert_snapshot;
286
287    fn selection_set(schema: &Valid<Schema>, s: &str) -> (ExecutableDocument, SelectionSet) {
288        let document = ExecutableDocument::parse_and_validate(schema, s, "./").unwrap();
289        let selection_set = document
290            .operations
291            .anonymous
292            .as_ref()
293            .unwrap()
294            .selection_set
295            .fields()
296            .next()
297            .unwrap()
298            .selection_set
299            .clone();
300        (document.into_inner(), selection_set)
301    }
302
303    #[test]
304    fn test() {
305        let json = super::JSONSelection::parse(
306            r###"
307        $.result {
308          a
309          b: c
310          d: e.f
311          g
312          h: 'i-j'
313          k: { l m: n }
314        }
315        "###,
316        )
317        .unwrap();
318
319        let schema = Schema::parse_and_validate(
320            r###"
321            type Query {
322                t: T
323            }
324
325            type T {
326                a: String
327                b: String
328                d: String
329                g: String
330                h: String
331                k: K
332            }
333
334            type K {
335              l: String
336              m: String
337            }
338            "###,
339            "./",
340        )
341        .unwrap();
342
343        let (document, selection_set) = selection_set(
344            &schema,
345            "{ t { z: a, y: b, x: d, w: h v: k { u: l t: m } } }",
346        );
347
348        let transformed =
349            json.apply_selection_set(&IndexSet::default(), &document, &selection_set, None);
350        assert_eq!(
351            transformed.to_string(),
352            r###"$.result {
353  a
354  b: c
355  d: e.f
356  h: "i-j"
357  k: {
358    l
359    m: n
360  }
361}"###
362        );
363    }
364
365    #[test]
366    fn test_star() {
367        let json_selection = super::JSONSelection::parse(
368            r###"
369        $.result {
370          a
371          b_alias: b
372          c {
373            d
374            e_alias: e
375            h: "h"
376            i: "i"
377            group: {
378              j
379              k
380            }
381          }
382          path_to_f: c.f
383        }
384        "###,
385        )
386        .unwrap();
387
388        let schema = Schema::parse_and_validate(
389            r###"
390            type Query {
391                t: T
392            }
393
394            type T {
395                a: String
396                b_alias: String
397                c: C
398                path_to_f: String
399            }
400
401            type C {
402                d: String
403                e_alias: String
404                h: String
405                i: String
406                group: Group
407            }
408
409            type Group {
410                j: String
411                k: String
412            }
413            "###,
414            "./",
415        )
416        .unwrap();
417
418        let (document, selection_set) = selection_set(
419            &schema,
420            "{ t { a b_alias c { e: e_alias h group { j } } path_to_f } }",
421        );
422
423        let transformed = json_selection.apply_selection_set(
424            &IndexSet::default(),
425            &document,
426            &selection_set,
427            None,
428        );
429        assert_eq!(
430            transformed.to_string(),
431            r###"$.result {
432  a
433  b_alias: b
434  c {
435    e_alias: e
436    h: "h"
437    group: {
438      j
439    }
440  }
441  path_to_f: c.f
442}"###
443        );
444
445        let data = serde_json_bytes::json!({
446            "result": {
447                "a": "a",
448                "b": "b",
449                "c": {
450                  "d": "d",
451                  "e": "e",
452                  "f": "f",
453                  "g": "g",
454                  "h": "h",
455                  "i": "i",
456                  "j": "j",
457                  "k": "k",
458                },
459            }
460        });
461        let result = transformed.apply_to(&data);
462        assert_eq!(
463            result,
464            (
465                Some(serde_json_bytes::json!(
466                {
467                    "a": "a",
468                    "b_alias": "b",
469                    "c": {
470                        "e_alias": "e",
471                        "h": "h",
472                        "group": {
473                          "j": "j"
474                        },
475                    },
476                    "path_to_f": "f",
477                })),
478                vec![]
479            )
480        );
481    }
482
483    #[test]
484    fn test_depth() {
485        let json = super::JSONSelection::parse(
486            r###"
487        $.result {
488          a {
489            b {
490              renamed: c
491            }
492          }
493        }
494        "###,
495        )
496        .unwrap();
497
498        let schema = Schema::parse_and_validate(
499            r###"
500            type Query {
501                t: T
502            }
503
504            type T {
505              a: A
506            }
507
508            type A {
509              b: B
510            }
511
512            type B {
513              renamed: String
514            }
515            "###,
516            "./",
517        )
518        .unwrap();
519
520        let (document, selection_set) = selection_set(&schema, "{ t { a { b { renamed } } } }");
521
522        let transformed =
523            json.apply_selection_set(&IndexSet::default(), &document, &selection_set, None);
524        assert_eq!(
525            transformed.to_string(),
526            r###"$.result {
527  a {
528    b {
529      renamed: c
530    }
531  }
532}"###
533        );
534
535        let data = serde_json_bytes::json!({
536            "result": {
537              "a": {
538                "b": {
539                  "c": "c",
540                }
541              }
542            }
543          }
544        );
545        let result = transformed.apply_to(&data);
546        assert_eq!(
547            result,
548            (
549                Some(serde_json_bytes::json!({"a": { "b": { "renamed": "c" } } } )),
550                vec![]
551            )
552        );
553    }
554
555    #[test]
556    fn test_typename() {
557        let json = super::JSONSelection::parse(
558            r###"
559            $.result {
560              id
561              author: {
562                id: authorId
563              }
564            }
565            "###,
566        )
567        .unwrap();
568
569        let schema = Schema::parse_and_validate(
570            r###"
571        type Query {
572            t: T
573        }
574
575        type T {
576            id: ID
577            author: A
578        }
579
580        type A {
581            id: ID
582        }
583        "###,
584            "./",
585        )
586        .unwrap();
587
588        let (document, selection_set) =
589            selection_set(&schema, "{ t { id __typename author { __typename id } } }");
590
591        let transformed =
592            json.apply_selection_set(&IndexSet::default(), &document, &selection_set, None);
593        assert_eq!(
594            transformed.to_string(),
595            r###"$.result {
596  __typename: $("T")
597  id
598  author: {
599    __typename: $("A")
600    id: authorId
601  }
602}"###
603        );
604    }
605
606    #[test]
607    fn test_fragments() {
608        let json = super::JSONSelection::parse(
609            r###"
610            reviews: result {
611                id
612                product: { upc: product_upc }
613                author: { id: author_id }
614            }
615            "###,
616        )
617        .unwrap();
618
619        let schema = Schema::parse_and_validate(
620            r###"
621        type Query {
622            _entities(representations: [_Any!]!): [_Entity]
623        }
624
625        scalar _Any
626
627        union _Entity = Product
628
629        type Product {
630            upc: String
631            reviews: [Review]
632        }
633
634        type Review {
635            id: ID
636            product: Product
637            author: User
638        }
639
640        type User {
641            id: ID
642        }
643        "###,
644            "./",
645        )
646        .unwrap();
647
648        let (document, selection_set) = selection_set(
649            &schema,
650            "query ($representations: [_Any!]!) {
651                _entities(representations: $representations) {
652                    ..._generated_onProduct1_0
653                }
654            }
655            fragment _generated_onProduct1_0 on Product {
656                reviews {
657                    id
658                    product {
659                        __typename
660                        upc
661                    }
662                    author {
663                        __typename
664                        id
665                    }
666                }
667            }",
668        );
669
670        let transformed =
671            json.apply_selection_set(&IndexSet::default(), &document, &selection_set, None);
672        assert_eq!(
673            transformed.to_string(),
674            r###"reviews: result {
675  id
676  product: {
677    __typename: $("Product")
678    upc: product_upc
679  }
680  author: {
681    __typename: $("User")
682    id: author_id
683  }
684}"###
685        );
686    }
687
688    #[test]
689    fn test_ensuring_key_fields() {
690        let json = super::JSONSelection::parse(
691            r###"
692            id
693            store { id }
694            name
695            price
696            "###,
697        )
698        .unwrap();
699
700        let schema = Schema::parse_and_validate(
701            r###"
702        type Query {
703            product: Product
704        }
705
706        type Product {
707            id: ID!
708            store: Store!
709            name: String!
710            price: String
711        }
712
713        type Store {
714          id: ID!
715        }
716        "###,
717            "./",
718        )
719        .unwrap();
720
721        let (document, selection_set) = selection_set(&schema, "{ product { name price } }");
722
723        let keys =
724            FieldSet::parse_and_validate(&schema, name!(Product), "id store { id }", "").unwrap();
725
726        let transformed =
727            json.apply_selection_set(&IndexSet::default(), &document, &selection_set, Some(&keys));
728        assert_snapshot!(transformed.to_string(), @r"
729        id
730        store {
731          id
732        }
733        name
734        price
735        ");
736    }
737}