Repository: apollographql/apollo-rs
Pull request #124 (2021-12-01 22:50:36)

chore: add tests for unterminated strings and invalid type system extensions
follows up on #122 and adds tests for the incorrectly lexed and parsed inputs that fuzzing discovered.

This commit also changes logic around having an "unexpected end of data" for `""` string. This now gets lexed into a `StringValue` token.


Patch:
diff --git a/crates/apollo-parser/src/lexer/mod.rs b/crates/apollo-parser/src/lexer/mod.rs
index f3c02b3..39516ba 100644
--- a/crates/apollo-parser/src/lexer/mod.rs
+++ b/crates/apollo-parser/src/lexer/mod.rs
@@ -105,7 +105,7 @@ impl Cursor<'_> {
         let c = match self.bump() {
             None => {
                 return Err(Error::new(
-                    "unexpected end of data while parsing string",
+                    "unexpected end of data while lexing string value",
                     "\"".to_string(),
                 ));
             }
@@ -137,15 +137,12 @@ impl Cursor<'_> {
         }
     }
 
-    fn block_string_value(&mut self, mut buf: String, first_char: char) -> Result<Token, Error> {
-        buf.push(first_char); // the second " we already matched on
+    fn block_string_value(&mut self, mut buf: String, char: char) -> Result<Token, Error> {
+        buf.push(char); // the second " we already matched on
 
         let c = match self.bump() {
             None => {
-                return Err(Error::new(
-                    "unexpected end of data while parsing block string",
-                    "\"\"".to_string(),
-                ));
+                return Ok(Token::new(TokenKind::StringValue, buf));
             }
             Some(c) => c,
         };
diff --git a/crates/apollo-parser/src/parser/grammar/document.rs b/crates/apollo-parser/src/parser/grammar/document.rs
index 4127870..5670538 100644
--- a/crates/apollo-parser/src/parser/grammar/document.rs
+++ b/crates/apollo-parser/src/parser/grammar/document.rs
@@ -88,10 +88,9 @@ uasdf21230jkdw
         let parser = Parser::new(schema);
 
         let ast = parser.parse();
-        assert_eq!(ast.errors().len(), 1);
 
-        let doc = ast.document();
-        assert_eq!(doc.definitions().into_iter().count(), 1);
+        assert_eq!(ast.errors().len(), 1);
+        assert_eq!(ast.document().definitions().into_iter().count(), 1);
     }
 
     #[test]
diff --git a/crates/apollo-parser/src/parser/grammar/extensions.rs b/crates/apollo-parser/src/parser/grammar/extensions.rs
index 52b09cc..a4538fe 100644
--- a/crates/apollo-parser/src/parser/grammar/extensions.rs
+++ b/crates/apollo-parser/src/parser/grammar/extensions.rs
@@ -14,6 +14,145 @@ pub(crate) fn extensions(p: &mut Parser) {
         Some("union") => union_::union_type_extension(p),
         Some("enum") => enum_::enum_type_extension(p),
         Some("input") => input::input_object_type_extension(p),
-        _ => p.err_and_pop("A Type System Extension cannot be applied"),
+        _ => p.err_and_pop("Invalid Type System Extension. This extension cannot be applied."),
+    }
+}
+
+#[cfg(test)]
+
+mod test {
+    use crate::{ast, Parser};
+
+    #[test]
+    fn it_queries_graphql_extensions() {
+        let gql = r#"
+extend schema {
+    mutation: MyMutationType
+}
+extend scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")
+extend type Business implements NamedEntity
+extend interface NamedEntity {
+    name: String
+}
+extend union SearchResult = Pet
+extend enum Pet {
+    GuineaPig
+    Cat
+}
+extend input First @include(if: "first")
+        "#;
+
+        let parser = Parser::new(gql);
+        let ast = parser.parse();
+
+        assert!(ast.errors().len() == 0);
+
+        let doc = ast.document();
+
+        for definition in doc.definitions() {
+            match definition {
+                ast::Definition::SchemaExtension(schema_ext) => {
+                    let root_operation_type: Vec<String> = schema_ext
+                        .root_operation_type_definitions()
+                        .filter_map(|def| Some(def.named_type()?.name()?.text().to_string()))
+                        .collect();
+                    assert_eq!(
+                        root_operation_type.as_slice(),
+                        ["MyMutationType".to_string()]
+                    )
+                }
+                ast::Definition::ScalarTypeExtension(scalar_ext) => {
+                    assert_eq!(
+                        scalar_ext
+                            .name()
+                            .expect("Cannot get scalar type extension name.")
+                            .text()
+                            .as_ref(),
+                        "UUID"
+                    );
+                }
+                ast::Definition::ObjectTypeExtension(obj_ext) => {
+                    assert_eq!(
+                        obj_ext
+                            .name()
+                            .expect("Cannot get object type extension name.")
+                            .text()
+                            .as_ref(),
+                        "Business"
+                    );
+                }
+                ast::Definition::InterfaceTypeExtension(interface_ext) => {
+                    assert_eq!(
+                        interface_ext
+                            .name()
+                            .expect("Cannot get interface type extension name.")
+                            .text()
+                            .as_ref(),
+                        "NamedEntity"
+                    );
+                }
+                ast::Definition::UnionTypeExtension(union_ext) => {
+                    assert_eq!(
+                        union_ext
+                            .name()
+                            .expect("Cannot get union type extension name.")
+                            .text()
+                            .as_ref(),
+                        "SearchResult"
+                    );
+                }
+                ast::Definition::EnumTypeExtension(enum_ext) => {
+                    assert_eq!(
+                        enum_ext
+                            .name()
+                            .expect("Cannot get enum type extension name.")
+                            .text()
+                            .as_ref(),
+                        "Pet"
+                    );
+                }
+                ast::Definition::InputObjectTypeExtension(input_object_ext) => {
+                    assert_eq!(
+                        input_object_ext
+                            .name()
+                            .expect("Cannot get input object type extension name.")
+                            .text()
+                            .as_ref(),
+                        "First"
+                    );
+                }
+                _ => unimplemented!(),
+            }
+        }
+    }
+
+    #[test]
+    fn it_reports_an_error_for_invalid_type_system_extension() {
+        let gql = r#"
+extend Cat
+        "#;
+
+        let parser = Parser::new(gql);
+        let ast = parser.parse();
+
+        assert!(ast.errors().len() == 2);
+        assert_eq!(ast.document().definitions().into_iter().count(), 0);
+    }
+
+    #[test]
+    fn it_continuous_parsing_when_an_invalid_extension_is_given() {
+        let gql = r#"
+extend Cat
+
+extend interface NamedEntity {
+    name: String
+}
+        "#;
+
+        let parser = Parser::new(gql);
+        let ast = parser.parse();
+
+        assert!(ast.errors().len() == 2);
+        assert_eq!(ast.document().definitions().into_iter().count(), 1);
     }
 }


Test patch:
diff --git a/crates/apollo-parser/src/tests.rs b/crates/apollo-parser/src/tests.rs
index abae1b1..fc0a649 100644
--- a/crates/apollo-parser/src/tests.rs
+++ b/crates/apollo-parser/src/tests.rs
@@ -15,7 +15,7 @@ use crate::{Error, Lexer, Parser, Token};
 
 // To run these tests and update files:
 // ```bash
-// env UPDATE_EXPECT=1 cargo qt
+// env UPDATE_EXPECT=1 cargo test --package apollo-parser
 // ```
 // or on windows
 // ```bash
diff --git a/crates/apollo-parser/test_data/lexer/err/0004_unterminated_string_value.graphql b/crates/apollo-parser/test_data/lexer/err/0004_unterminated_string_value.graphql
new file mode 100644
index 0000000..9d68933
--- /dev/null
+++ b/crates/apollo-parser/test_data/lexer/err/0004_unterminated_string_value.graphql
@@ -0,0 +1,1 @@
+"
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/lexer/err/0004_unterminated_string_value.txt b/crates/apollo-parser/test_data/lexer/err/0004_unterminated_string_value.txt
new file mode 100644
index 0000000..4eb207a
--- /dev/null
+++ b/crates/apollo-parser/test_data/lexer/err/0004_unterminated_string_value.txt
@@ -0,0 +1,2 @@
+EOF@1:1
+ERROR@0:1 "unexpected end of data while lexing string value" "
diff --git a/crates/apollo-parser/test_data/lexer/ok/0010_empty_string_value_followed_by_eof.graphql b/crates/apollo-parser/test_data/lexer/ok/0010_empty_string_value_followed_by_eof.graphql
new file mode 100644
index 0000000..3cc762b
--- /dev/null
+++ b/crates/apollo-parser/test_data/lexer/ok/0010_empty_string_value_followed_by_eof.graphql
@@ -0,0 +1,1 @@
+""
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/lexer/ok/0010_empty_string_value_followed_by_eof.txt b/crates/apollo-parser/test_data/lexer/ok/0010_empty_string_value_followed_by_eof.txt
new file mode 100644
index 0000000..2466c65
--- /dev/null
+++ b/crates/apollo-parser/test_data/lexer/ok/0010_empty_string_value_followed_by_eof.txt
@@ -0,0 +1,2 @@
+STRING_VALUE@0:2 "\"\""
+EOF@2:2
diff --git a/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.graphql b/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.graphql
new file mode 100644
index 0000000..d54d5e1
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.graphql
@@ -0,0 +1,1 @@
+extend Cat
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.txt b/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.txt
new file mode 100644
index 0000000..dc7e20e
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0027_invalid_type_system_extension.txt
@@ -0,0 +1,4 @@
+- DOCUMENT@0..1
+    - WHITESPACE@0..1 " "
+- ERROR@0:6 "Invalid Type System Extension. This extension cannot be applied." extend
+- ERROR@7:10 "expected definition" Cat
diff --git a/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.graphql b/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.graphql
new file mode 100644
index 0000000..c1ed29d
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.graphql
@@ -0,0 +1,5 @@
+extend Cat
+
+extend interface NamedEntity {
+    name: String
+}
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.txt b/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.txt
new file mode 100644
index 0000000..365bfe1
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0028_invalid_type_system_extension_followed_by_valid.txt
@@ -0,0 +1,27 @@
+- DOCUMENT@0..52
+    - WHITESPACE@0..1 " "
+    - WHITESPACE@1..3 "\n\n"
+    - INTERFACE_TYPE_EXTENSION@3..52
+        - extend_KW@3..9 "extend"
+        - WHITESPACE@9..10 " "
+        - interface_KW@10..19 "interface"
+        - WHITESPACE@19..20 " "
+        - NAME@20..32
+            - IDENT@20..31 "NamedEntity"
+            - WHITESPACE@31..32 " "
+        - FIELDS_DEFINITION@32..52
+            - L_CURLY@32..33 "{"
+            - WHITESPACE@33..38 "\n    "
+            - FIELD_DEFINITION@38..51
+                - NAME@38..42
+                    - IDENT@38..42 "name"
+                - COLON@42..43 ":"
+                - WHITESPACE@43..44 " "
+                - TYPE@44..51
+                    - WHITESPACE@44..45 "\n"
+                    - NAMED_TYPE@45..51
+                        - NAME@45..51
+                            - IDENT@45..51 "String"
+            - R_CURLY@51..52 "}"
+- ERROR@0:6 "Invalid Type System Extension. This extension cannot be applied." extend
+- ERROR@7:10 "expected definition" Cat