Skip to content

Commit 5bcc3a9

Browse files
authored
Add root_fields() and all_fields() to SelectionSet (#954)
1 parent 35f280c commit 5bcc3a9

File tree

1 file changed

+109
-69
lines changed
  • crates/apollo-compiler/src/executable

1 file changed

+109
-69
lines changed

crates/apollo-compiler/src/executable/mod.rs

Lines changed: 109 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -551,39 +551,7 @@ impl Operation {
551551
&'doc self,
552552
document: &'doc ExecutableDocument,
553553
) -> impl Iterator<Item = &'doc Node<Field>> {
554-
let mut stack = vec![self.selection_set.selections.iter()];
555-
let mut fragments_seen = HashSet::default();
556-
std::iter::from_fn(move || {
557-
while let Some(selection_set_iter) = stack.last_mut() {
558-
match selection_set_iter.next() {
559-
Some(Selection::Field(field)) => {
560-
// Yield one item from the `root_fields()` iterator
561-
// but ignore its sub-selections in `field.selection_set`
562-
return Some(field);
563-
}
564-
Some(Selection::InlineFragment(inline)) => {
565-
stack.push(inline.selection_set.selections.iter())
566-
}
567-
Some(Selection::FragmentSpread(spread)) => {
568-
if let Some(def) = document.fragments.get(&spread.fragment_name) {
569-
let new = fragments_seen.insert(&spread.fragment_name);
570-
if new {
571-
stack.push(def.selection_set.selections.iter())
572-
}
573-
} else {
574-
// Undefined fragments are silently ignored.
575-
// They should never happen in a valid document.
576-
}
577-
}
578-
None => {
579-
// Remove an empty iterator from the stack
580-
// and continue with the parent selection set
581-
stack.pop();
582-
}
583-
}
584-
}
585-
None
586-
})
554+
self.selection_set.root_fields(document)
587555
}
588556

589557
/// Returns an iterator of all field selections in this operation.
@@ -602,42 +570,7 @@ impl Operation {
602570
&'doc self,
603571
document: &'doc ExecutableDocument,
604572
) -> impl Iterator<Item = &'doc Node<Field>> {
605-
let mut stack = vec![self.selection_set.selections.iter()];
606-
let mut fragments_seen = HashSet::default();
607-
std::iter::from_fn(move || {
608-
while let Some(selection_set_iter) = stack.last_mut() {
609-
match selection_set_iter.next() {
610-
Some(Selection::Field(field)) => {
611-
if !field.selection_set.is_empty() {
612-
// Will be considered for the next call
613-
stack.push(field.selection_set.selections.iter())
614-
}
615-
// Yield one item from the `all_fields()` iterator
616-
return Some(field);
617-
}
618-
Some(Selection::InlineFragment(inline)) => {
619-
stack.push(inline.selection_set.selections.iter())
620-
}
621-
Some(Selection::FragmentSpread(spread)) => {
622-
if let Some(def) = document.fragments.get(&spread.fragment_name) {
623-
let new = fragments_seen.insert(&spread.fragment_name);
624-
if new {
625-
stack.push(def.selection_set.selections.iter())
626-
}
627-
} else {
628-
// Undefined fragments are silently ignored.
629-
// They should never happen in a valid document.
630-
}
631-
}
632-
None => {
633-
// Remove an empty iterator from the stack
634-
// and continue with the parent selection set
635-
stack.pop();
636-
}
637-
}
638-
}
639-
None
640-
})
573+
self.selection_set.all_fields(document)
641574
}
642575

643576
serialize_method!();
@@ -707,6 +640,113 @@ impl SelectionSet {
707640
self.selections.iter().filter_map(|sel| sel.as_field())
708641
}
709642

643+
/// Returns an iterator of field selections that are at the root of the response.
644+
/// That is, inline fragments and fragment spreads at the root are traversed,
645+
/// but field sub-selections are not.
646+
///
647+
/// See also [`all_fields`][Self::all_fields].
648+
///
649+
/// `document` is used to look up fragment definitions.
650+
///
651+
/// This does **not** perform [field merging],
652+
/// so multiple items in this iterator may have the same response key
653+
/// or point to the same field definition.
654+
/// Named fragments however are only traversed once even if spread multiple times.
655+
///
656+
/// [field merging]: https://spec.graphql.org/draft/#sec-Field-Selection-Merging
657+
pub fn root_fields<'doc>(
658+
&'doc self,
659+
document: &'doc ExecutableDocument,
660+
) -> impl Iterator<Item = &'doc Node<Field>> {
661+
let mut stack = vec![self.selections.iter()];
662+
let mut fragments_seen = HashSet::default();
663+
std::iter::from_fn(move || {
664+
while let Some(selection_set_iter) = stack.last_mut() {
665+
match selection_set_iter.next() {
666+
Some(Selection::Field(field)) => {
667+
// Yield one item from the `root_fields()` iterator
668+
// but ignore its sub-selections in `field.selection_set`
669+
return Some(field);
670+
}
671+
Some(Selection::InlineFragment(inline)) => {
672+
stack.push(inline.selection_set.selections.iter())
673+
}
674+
Some(Selection::FragmentSpread(spread)) => {
675+
if let Some(def) = document.fragments.get(&spread.fragment_name) {
676+
let new = fragments_seen.insert(&spread.fragment_name);
677+
if new {
678+
stack.push(def.selection_set.selections.iter())
679+
}
680+
} else {
681+
// Undefined fragments are silently ignored.
682+
// They should never happen in a valid document.
683+
}
684+
}
685+
None => {
686+
// Remove an empty iterator from the stack
687+
// and continue with the parent selection set
688+
stack.pop();
689+
}
690+
}
691+
}
692+
None
693+
})
694+
}
695+
696+
/// Returns an iterator of all field selections in this operation.
697+
///
698+
/// See also [`root_fields`][Self::root_fields].
699+
///
700+
/// `document` is used to look up fragment definitions.
701+
///
702+
/// This does **not** perform [field merging],
703+
/// so multiple items in this iterator may have the same response key
704+
/// or point to the same field definition.
705+
/// Named fragments however are only traversed once even if spread multiple times.
706+
///
707+
/// [field merging]: https://spec.graphql.org/draft/#sec-Field-Selection-Merging
708+
pub fn all_fields<'doc>(
709+
&'doc self,
710+
document: &'doc ExecutableDocument,
711+
) -> impl Iterator<Item = &'doc Node<Field>> {
712+
let mut stack = vec![self.selections.iter()];
713+
let mut fragments_seen = HashSet::default();
714+
std::iter::from_fn(move || {
715+
while let Some(selection_set_iter) = stack.last_mut() {
716+
match selection_set_iter.next() {
717+
Some(Selection::Field(field)) => {
718+
if !field.selection_set.is_empty() {
719+
// Will be considered for the next call
720+
stack.push(field.selection_set.selections.iter())
721+
}
722+
// Yield one item from the `all_fields()` iterator
723+
return Some(field);
724+
}
725+
Some(Selection::InlineFragment(inline)) => {
726+
stack.push(inline.selection_set.selections.iter())
727+
}
728+
Some(Selection::FragmentSpread(spread)) => {
729+
if let Some(def) = document.fragments.get(&spread.fragment_name) {
730+
let new = fragments_seen.insert(&spread.fragment_name);
731+
if new {
732+
stack.push(def.selection_set.selections.iter())
733+
}
734+
} else {
735+
// Undefined fragments are silently ignored.
736+
// They should never happen in a valid document.
737+
}
738+
}
739+
None => {
740+
// Remove an empty iterator from the stack
741+
// and continue with the parent selection set
742+
stack.pop();
743+
}
744+
}
745+
}
746+
None
747+
})
748+
}
749+
710750
serialize_method!();
711751
}
712752

0 commit comments

Comments
 (0)