Repository: apollographql/apollo-rs (rust)

Issue:
How to get field names from selection set?
<!--
  Before opening a new issue, please search existing issues: https://github.com/apollographql/apollo-rs/issues
-->

## Description

Given the query below, I'd like to know how to extract the field names (`id`, `field1`, `field2`) from the selection set.

```
let query = r#"query SomeQuery(
  $param1: String!
  $param2: String!
) {
  item1(
    param1: $param1
    param2: $param2
  ) {
    id
    field1
    field2
    field3 {
      field1
      field2
    }
  }
}
```

Pull request description:
chore(apollo-parser): add a test for nested SELECTION_SETs
This will mostly act as an example in case users are looking for how to work with nested selections and get their FIELD/INLINE_FRAGMENT/FRAGMENT_SPREAD.

closes #136 

Patch:
diff --git a/crates/apollo-parser/src/parser/grammar/selection.rs b/crates/apollo-parser/src/parser/grammar/selection.rs
index 8808ae2..534256f 100644
--- a/crates/apollo-parser/src/parser/grammar/selection.rs
+++ b/crates/apollo-parser/src/parser/grammar/selection.rs
@@ -57,10 +57,7 @@ pub(crate) fn selection(p: &mut Parser) {
 
 #[cfg(test)]
 mod test {
-    use crate::{
-        ast::{Definition, Selection, SelectionSet, Value},
-        Parser, TokenText,
-    };
+    use crate::{ast, Parser, TokenText};
 
     #[test]
     fn fragment_spread_in_selection() {
@@ -82,17 +79,17 @@ mod test {
         let doc = ast.document();
 
         for def in doc.definitions() {
-            if let Definition::OperationDefinition(op_def) = def {
+            if let ast::Definition::OperationDefinition(op_def) = def {
                 if let Some(selection_set) = op_def.selection_set() {
                     for selection in selection_set.selections() {
                         match selection {
-                            Selection::Field(field) => {
+                            ast::Selection::Field(field) => {
                                 assert_eq!(
                                     "animal".to_string(),
                                     field.name().unwrap().text().to_string()
                                 );
                             }
-                            Selection::FragmentSpread(f_spread) => {
+                            ast::Selection::FragmentSpread(f_spread) => {
                                 assert_eq!(
                                     "snackSelection".to_string(),
                                     f_spread
@@ -104,7 +101,7 @@ mod test {
                                         .to_string()
                                 )
                             }
-                            Selection::InlineFragment(inline_fragment) => {
+                            ast::Selection::InlineFragment(inline_fragment) => {
                                 assert_eq!(
                                     "Pet".to_string(),
                                     inline_fragment
@@ -143,7 +140,7 @@ query GraphQuery($graph_id: ID!, $variant: String) {
         let doc = ast.document();
 
         for def in doc.definitions() {
-            if let Definition::OperationDefinition(op_def) = def {
+            if let ast::Definition::OperationDefinition(op_def) = def {
                 assert_eq!(op_def.name().unwrap().text(), "GraphQuery");
 
                 let variable_defs = op_def.variable_definitions();
@@ -164,18 +161,18 @@ query GraphQuery($graph_id: ID!, $variant: String) {
 
         fn get_variables_from_selection(
             used_vars: &mut Vec<TokenText>,
-            selection_set: SelectionSet,
+            selection_set: ast::SelectionSet,
         ) -> &Vec<TokenText> {
             for selection in selection_set.selections() {
                 match selection {
-                    Selection::Field(field) => {
+                    ast::Selection::Field(field) => {
                         let arguments = field.arguments();
                         let mut vars: Vec<TokenText> = arguments
                             .iter()
                             .map(|a| a.arguments())
                             .flatten()
                             .filter_map(|v| {
-                                if let Value::Variable(var) = v.value()? {
+                                if let ast::Value::Variable(var) = v.value()? {
                                     dbg!(&var.name()?.text());
                                     return Some(var.name()?.text());
                                 }
@@ -199,6 +196,78 @@ query GraphQuery($graph_id: ID!, $variant: String) {
         }
     }
 
+    #[test]
+    fn it_gets_nested_selection_set_fields() {
+        let query = r#"
+query SomeQuery(
+  $param1: String!
+  $param2: String!
+) {
+  item1(
+    param1: $param1
+    param2: $param2
+  ) {
+    id
+    ...Fragment1
+    ... on Fragment2 {
+      field3 {
+        field4
+      }
+    }
+  }
+}"#;
+        let parser = Parser::new(query);
+        let ast = parser.parse();
+
+        assert_eq!(ast.errors().len(), 0);
+
+        let doc = ast.document();
+        for def in doc.definitions() {
+            if let ast::Definition::OperationDefinition(op_def) = def {
+                let selection_set = op_def.selection_set().unwrap();
+                for selection in selection_set.selections() {
+                    if let ast::Selection::Field(field) = selection {
+                        assert_eq!("item1", field.name().unwrap().text().as_ref());
+                        let selection_set = field.selection_set().unwrap();
+                        for selection in selection_set.selections() {
+                            match selection {
+                                ast::Selection::Field(field) => {
+                                    assert_eq!("id", field.name().unwrap().text().as_ref());
+                                }
+                                ast::Selection::FragmentSpread(fragment_spread) => {
+                                    assert_eq!(
+                                        "Fragment1",
+                                        fragment_spread
+                                            .fragment_name()
+                                            .unwrap()
+                                            .name()
+                                            .unwrap()
+                                            .text()
+                                            .as_ref()
+                                    );
+                                }
+                                ast::Selection::InlineFragment(inline_fragment) => {
+                                    assert_eq!(
+                                        "Fragment2",
+                                        inline_fragment
+                                            .type_condition()
+                                            .unwrap()
+                                            .named_type()
+                                            .unwrap()
+                                            .name()
+                                            .unwrap()
+                                            .text()
+                                            .as_ref()
+                                    );
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     #[test]
     fn it_errors_when_selection_set_is_empty() {
         let schema = r#"


Test patch:
diff --git a/crates/apollo-parser/test_data/parser/ok/0037_operation_type_definition_with_inline_fragment.graphql b/crates/apollo-parser/test_data/parser/ok/0037_operation_type_definition_with_inline_fragment.graphql
new file mode 100644
index 0000000..314fff8
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/ok/0037_operation_type_definition_with_inline_fragment.graphql
@@ -0,0 +1,16 @@
+query SomeQuery(
+  $param1: String!
+  $param2: String!
+) {
+  item1(
+    param1: $param1
+    param2: $param2
+  ) {
+    id
+    ... on Fragment1 {
+      field3 {
+        field4
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0037_operation_type_definition_with_inline_fragment.txt b/crates/apollo-parser/test_data/parser/ok/0037_operation_type_definition_with_inline_fragment.txt
new file mode 100644
index 0000000..6c46097
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/ok/0037_operation_type_definition_with_inline_fragment.txt
@@ -0,0 +1,105 @@
+- DOCUMENT@0..191
+    - OPERATION_DEFINITION@0..191
+        - OPERATION_TYPE@0..6
+            - query_KW@0..5 "query"
+            - WHITESPACE@5..6 " "
+        - NAME@6..15
+            - IDENT@6..15 "SomeQuery"
+        - VARIABLE_DEFINITIONS@15..55
+            - L_PAREN@15..16 "("
+            - WHITESPACE@16..19 "\n  "
+            - VARIABLE_DEFINITION@19..37
+                - VARIABLE@19..26
+                    - DOLLAR@19..20 "$"
+                    - NAME@20..26
+                        - IDENT@20..26 "param1"
+                - COLON@26..27 ":"
+                - WHITESPACE@27..28 " "
+                - NON_NULL_TYPE@28..37
+                    - WHITESPACE@28..31 "\n  "
+                    - NAMED_TYPE@31..37
+                        - NAME@31..37
+                            - IDENT@31..37 "String"
+            - VARIABLE_DEFINITION@37..53
+                - VARIABLE@37..44
+                    - DOLLAR@37..38 "$"
+                    - NAME@38..44
+                        - IDENT@38..44 "param2"
+                - COLON@44..45 ":"
+                - WHITESPACE@45..46 " "
+                - NON_NULL_TYPE@46..53
+                    - WHITESPACE@46..47 "\n"
+                    - NAMED_TYPE@47..53
+                        - NAME@47..53
+                            - IDENT@47..53 "String"
+            - R_PAREN@53..54 ")"
+            - WHITESPACE@54..55 " "
+        - SELECTION_SET@55..191
+            - L_CURLY@55..56 "{"
+            - WHITESPACE@56..59 "\n  "
+            - FIELD@59..190
+                - NAME@59..64
+                    - IDENT@59..64 "item1"
+                - ARGUMENTS@64..110
+                    - L_PAREN@64..65 "("
+                    - WHITESPACE@65..70 "\n    "
+                    - ARGUMENT@70..90
+                        - NAME@70..76
+                            - IDENT@70..76 "param1"
+                        - COLON@76..77 ":"
+                        - WHITESPACE@77..78 " "
+                        - VARIABLE@78..90
+                            - DOLLAR@78..79 "$"
+                            - NAME@79..90
+                                - IDENT@79..85 "param1"
+                                - WHITESPACE@85..90 "\n    "
+                    - ARGUMENT@90..108
+                        - NAME@90..96
+                            - IDENT@90..96 "param2"
+                        - COLON@96..97 ":"
+                        - WHITESPACE@97..98 " "
+                        - VARIABLE@98..108
+                            - DOLLAR@98..99 "$"
+                            - NAME@99..108
+                                - IDENT@99..105 "param2"
+                                - WHITESPACE@105..108 "\n  "
+                    - R_PAREN@108..109 ")"
+                    - WHITESPACE@109..110 " "
+                - SELECTION_SET@110..190
+                    - L_CURLY@110..111 "{"
+                    - WHITESPACE@111..116 "\n    "
+                    - FIELD@116..123
+                        - NAME@116..123
+                            - IDENT@116..118 "id"
+                            - WHITESPACE@118..123 "\n    "
+                    - INLINE_FRAGMENT@123..188
+                        - SPREAD@123..126 "..."
+                        - WHITESPACE@126..127 " "
+                        - TYPE_CONDITION@127..140
+                            - on_KW@127..129 "on"
+                            - WHITESPACE@129..130 " "
+                            - NAMED_TYPE@130..140
+                                - NAME@130..140
+                                    - IDENT@130..139 "Fragment1"
+                                    - WHITESPACE@139..140 " "
+                        - SELECTION_SET@140..188
+                            - L_CURLY@140..141 "{"
+                            - WHITESPACE@141..148 "\n      "
+                            - FIELD@148..184
+                                - NAME@148..155
+                                    - IDENT@148..154 "field3"
+                                    - WHITESPACE@154..155 " "
+                                - SELECTION_SET@155..184
+                                    - L_CURLY@155..156 "{"
+                                    - WHITESPACE@156..165 "\n        "
+                                    - FIELD@165..178
+                                        - NAME@165..178
+                                            - IDENT@165..171 "field4"
+                                            - WHITESPACE@171..178 "\n      "
+                                    - R_CURLY@178..179 "}"
+                                    - WHITESPACE@179..184 "\n    "
+                            - R_CURLY@184..185 "}"
+                            - WHITESPACE@185..188 "\n  "
+                    - R_CURLY@188..189 "}"
+                    - WHITESPACE@189..190 "\n"
+            - R_CURLY@190..191 "}"