Repository: apollographql/apollo-rs
Pull request #847 (2024-03-26 22:21:39)

Implement missing schema validation - types must have one or more (fields, enum values, members, input values)
## Description 

Per the [GraphQL spec]( https://spec.graphql.org/draft/#sel-FAHZhCFDBAACDA4qe), object, interface, enum, union, and input types must define one or more fields, enum values, members, input values respectively across all extensions.

This PR implements the necessary validation for each of the aforementioned types.

As an example, previously, the following SDL would incorrectly pass schema validation:
```
directive @foo on OBJECT
type Query {
  foo: User
}
type User 
extend type User @foo
```
`User` does not define any fields, even across its extensions.


Patch:
diff --git a/crates/apollo-compiler/src/validation/diagnostics.rs b/crates/apollo-compiler/src/validation/diagnostics.rs
index d2eba2b..eca5c0f 100644
--- a/crates/apollo-compiler/src/validation/diagnostics.rs
+++ b/crates/apollo-compiler/src/validation/diagnostics.rs
@@ -261,6 +261,30 @@ pub(crate) enum DiagnosticData {
     },
     #[error("too much recursion")]
     RecursionError {},
+    #[error("`{type_name}` has no fields")]
+    EmptyFieldSet {
+        type_name: Name,
+        type_location: Option<NodeLocation>,
+        extensions_locations: Vec<Option<NodeLocation>>,
+    },
+    #[error("`{type_name}` has no enum values")]
+    EmptyValueSet {
+        type_name: Name,
+        type_location: Option<NodeLocation>,
+        extensions_locations: Vec<Option<NodeLocation>>,
+    },
+    #[error("`{type_name}` has no member types")]
+    EmptyMemberSet {
+        type_name: Name,
+        type_location: Option<NodeLocation>,
+        extensions_locations: Vec<Option<NodeLocation>>,
+    },
+    #[error("`{type_name}` has no input values")]
+    EmptyInputValueSet {
+        type_name: Name,
+        type_location: Option<NodeLocation>,
+        extensions_locations: Vec<Option<NodeLocation>>,
+    },
 }
 
 impl DiagnosticData {
@@ -630,8 +654,87 @@ impl DiagnosticData {
                 );
             }
             DiagnosticData::RecursionError {} => {}
+            DiagnosticData::EmptyFieldSet {
+                type_name,
+                type_location,
+                extensions_locations,
+            } => {
+                Self::report_empty_type(
+                    report,
+                    type_name,
+                    type_location,
+                    extensions_locations,
+                    "fields",
+                );
+            }
+            DiagnosticData::EmptyValueSet {
+                type_name,
+                type_location,
+                extensions_locations,
+            } => {
+                Self::report_empty_type(
+                    report,
+                    type_name,
+                    type_location,
+                    extensions_locations,
+                    "enum values",
+                );
+            }
+            DiagnosticData::EmptyMemberSet {
+                type_name,
+                type_location,
+                extensions_locations,
+            } => {
+                Self::report_empty_type(
+                    report,
+                    type_name,
+                    type_location,
+                    extensions_locations,
+                    "union member types",
+                );
+            }
+            DiagnosticData::EmptyInputValueSet {
+                type_name,
+                type_location,
+                extensions_locations,
+            } => {
+                Self::report_empty_type(
+                    report,
+                    type_name,
+                    type_location,
+                    extensions_locations,
+                    "input values",
+                );
+            }
         }
     }
+
+    fn report_empty_type(
+        report: &mut CliReport,
+        type_name: &Name,
+        type_location: &Option<NodeLocation>,
+        extensions_locations: &[Option<NodeLocation>],
+        describe_missing_kind: &str,
+    ) {
+        report.with_label_opt(
+            *type_location,
+            format_args!("{type_name} type defined here"),
+        );
+        extensions_locations.iter().for_each(|location| {
+            report.with_label_opt(
+                *location,
+                format_args!("{type_name} extension defined here"),
+            );
+        });
+        let and_extensions_message = if !extensions_locations.is_empty() {
+            " or its type extensions"
+        } else {
+            ""
+        };
+        report.with_help(format!(
+            "Define one or more {describe_missing_kind} on `{type_name}`{and_extensions_message}."
+        ));
+    }
 }
 
 fn label_recursive_trace<T>(
diff --git a/crates/apollo-compiler/src/validation/enum_.rs b/crates/apollo-compiler/src/validation/enum_.rs
index 140f5a9..ad8eb9c 100644
--- a/crates/apollo-compiler/src/validation/enum_.rs
+++ b/crates/apollo-compiler/src/validation/enum_.rs
@@ -1,4 +1,5 @@
 use crate::schema::{EnumType, ExtendedType};
+use crate::validation::diagnostics::DiagnosticData;
 use crate::validation::DiagnosticList;
 use crate::{ast, Node};
 
@@ -13,7 +14,7 @@ pub(crate) fn validate_enum_definitions(diagnostics: &mut DiagnosticList, schema
 pub(crate) fn validate_enum_definition(
     diagnostics: &mut DiagnosticList,
     schema: &crate::Schema,
-    enum_def: &EnumType,
+    enum_def: &Node<EnumType>,
 ) {
     super::directive::validate_directives(
         diagnostics,
@@ -27,6 +28,23 @@ pub(crate) fn validate_enum_definition(
     for enum_val in enum_def.values.values() {
         validate_enum_value(diagnostics, schema, enum_val);
     }
+
+    // validate there is at least one enum value on the enum type
+    // https://spec.graphql.org/draft/#sel-DAHfFVFBAAEXBAAh7S
+    if enum_def.values.is_empty() {
+        diagnostics.push(
+            enum_def.location(),
+            DiagnosticData::EmptyValueSet {
+                type_name: enum_def.name.clone(),
+                type_location: enum_def.location(),
+                extensions_locations: enum_def
+                    .extensions()
+                    .iter()
+                    .map(|ext| ext.location())
+                    .collect(),
+            },
+        );
+    }
 }
 
 pub(crate) fn validate_enum_value(
diff --git a/crates/apollo-compiler/src/validation/input_object.rs b/crates/apollo-compiler/src/validation/input_object.rs
index f6dcbdf..c1de993 100644
--- a/crates/apollo-compiler/src/validation/input_object.rs
+++ b/crates/apollo-compiler/src/validation/input_object.rs
@@ -119,6 +119,23 @@ pub(crate) fn validate_input_object_definition(
         &fields,
         ast::DirectiveLocation::InputFieldDefinition,
     );
+
+    // validate there is at least one input value on the input object type
+    // https://spec.graphql.org/draft/#sel-HAHhBXDBABAB5BvgD
+    if input_object.fields.is_empty() {
+        diagnostics.push(
+            input_object.location(),
+            DiagnosticData::EmptyInputValueSet {
+                type_name: input_object.name.clone(),
+                type_location: input_object.location(),
+                extensions_locations: input_object
+                    .extensions()
+                    .iter()
+                    .map(|ext| ext.location())
+                    .collect(),
+            },
+        );
+    }
 }
 
 pub(crate) fn validate_argument_definitions(
diff --git a/crates/apollo-compiler/src/validation/interface.rs b/crates/apollo-compiler/src/validation/interface.rs
index 843c522..b994f87 100644
--- a/crates/apollo-compiler/src/validation/interface.rs
+++ b/crates/apollo-compiler/src/validation/interface.rs
@@ -59,6 +59,23 @@ pub(crate) fn validate_interface_definition(
     // Interface Type field validation.
     validate_field_definitions(diagnostics, schema, &interface.fields);
 
+    // validate there is at least one field on the type
+    // https://spec.graphql.org/draft/#sel-HAHbnBFBABABxB4a
+    if interface.fields.is_empty() {
+        diagnostics.push(
+            interface.location(),
+            DiagnosticData::EmptyFieldSet {
+                type_name: interface.name.clone(),
+                type_location: interface.location(),
+                extensions_locations: interface
+                    .extensions()
+                    .iter()
+                    .map(|ext| ext.location())
+                    .collect(),
+            },
+        );
+    }
+
     // Implements Interfaceds validation.
     validate_implements_interfaces(
         diagnostics,
diff --git a/crates/apollo-compiler/src/validation/mod.rs b/crates/apollo-compiler/src/validation/mod.rs
index 668dd93..c4a07ed 100644
--- a/crates/apollo-compiler/src/validation/mod.rs
+++ b/crates/apollo-compiler/src/validation/mod.rs
@@ -252,6 +252,10 @@ impl DiagnosticData {
                     RecursiveInputObjectDefinition { .. } => "RecursiveInputObjectDefinition",
                     RecursiveFragmentDefinition { .. } => "RecursiveFragmentDefinition",
                     DeeplyNestedType { .. } => "DeeplyNestedType",
+                    EmptyFieldSet { .. } => "EmptyFieldSet",
+                    EmptyValueSet { .. } => "EmptyValueSet",
+                    EmptyMemberSet { .. } => "EmptyMemberSet",
+                    EmptyInputValueSet { .. } => "EmptyInputValueSet",
                 })
             }
             Details::ExecutableBuildError(error) => Some(match error {
diff --git a/crates/apollo-compiler/src/validation/object.rs b/crates/apollo-compiler/src/validation/object.rs
index 58e62af..85567ca 100644
--- a/crates/apollo-compiler/src/validation/object.rs
+++ b/crates/apollo-compiler/src/validation/object.rs
@@ -34,6 +34,23 @@ pub(crate) fn validate_object_type_definition(
     // Object Type field validations.
     validate_field_definitions(diagnostics, schema, &object.fields);
 
+    // validate there is at least one field on the type
+    // https://spec.graphql.org/draft/#sel-FAHZhCFDBAACDA4qe
+    if object.fields.is_empty() {
+        diagnostics.push(
+            object.location(),
+            DiagnosticData::EmptyFieldSet {
+                type_name: object.name.clone(),
+                type_location: object.location(),
+                extensions_locations: object
+                    .extensions()
+                    .iter()
+                    .map(|ext| ext.location())
+                    .collect(),
+            },
+        );
+    }
+
     // Implements Interfaces validation.
     super::interface::validate_implements_interfaces(
         diagnostics,
diff --git a/crates/apollo-compiler/src/validation/union_.rs b/crates/apollo-compiler/src/validation/union_.rs
index 9b784c1..386fd0e 100644
--- a/crates/apollo-compiler/src/validation/union_.rs
+++ b/crates/apollo-compiler/src/validation/union_.rs
@@ -52,4 +52,21 @@ pub(crate) fn validate_union_definition(
             }
         }
     }
+
+    // validate there is at least one union member on the union type
+    // https://spec.graphql.org/draft/#sel-HAHdfFBABAB6Bw3R
+    if union_def.members.is_empty() {
+        diagnostics.push(
+            union_def.location(),
+            DiagnosticData::EmptyMemberSet {
+                type_name: union_def.name.clone(),
+                type_location: union_def.location(),
+                extensions_locations: union_def
+                    .extensions()
+                    .iter()
+                    .map(|ext| ext.location())
+                    .collect(),
+            },
+        );
+    }
 }


Test patch:
diff --git a/crates/apollo-compiler/test_data/diagnostics/0080_directive_is_unique_with_extensions.graphql b/crates/apollo-compiler/test_data/diagnostics/0080_directive_is_unique_with_extensions.graphql
index 62a6cf9..535a469 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0080_directive_is_unique_with_extensions.graphql
+++ b/crates/apollo-compiler/test_data/diagnostics/0080_directive_is_unique_with_extensions.graphql
@@ -1,6 +1,8 @@
 directive @nonRepeatable on OBJECT | SCALAR | INTERFACE
 extend type TestObject @nonRepeatable
-type TestObject @nonRepeatable
+type TestObject @nonRepeatable {
+  field: String
+}
 extend type TestObject @nonRepeatable
 
 scalar Scalar @nonRepeatable
@@ -9,7 +11,7 @@ extend scalar Scalar @nonRepeatable @specifiedBy(url: "example.com")
 interface Intf @nonRepeatable {
   field: String
 }
-interface Intf @nonRepeatable
+extend interface Intf @nonRepeatable
 type Query {
   x: Int
 }
diff --git a/crates/apollo-compiler/test_data/diagnostics/0080_directive_is_unique_with_extensions.txt b/crates/apollo-compiler/test_data/diagnostics/0080_directive_is_unique_with_extensions.txt
index 8ee546a..1f35290 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0080_directive_is_unique_with_extensions.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0080_directive_is_unique_with_extensions.txt
@@ -4,41 +4,40 @@ Error: non-repeatable directive nonRepeatable can only be used once per location
  2 │ extend type TestObject @nonRepeatable
    │                        ───────┬──────  
    │                               ╰──────── directive `@nonRepeatable` called again here
- 3 │ type TestObject @nonRepeatable
+ 3 │ type TestObject @nonRepeatable {
    │                 ───────┬──────  
    │                        ╰──────── directive `@nonRepeatable` first called here
 ───╯
 Error: non-repeatable directive nonRepeatable can only be used once per location
-   ╭─[0080_directive_is_unique_with_extensions.graphql:4:24]
+   ╭─[0080_directive_is_unique_with_extensions.graphql:6:24]
    │
- 3 │ type TestObject @nonRepeatable
+ 3 │ type TestObject @nonRepeatable {
    │                 ───────┬──────  
    │                        ╰──────── directive `@nonRepeatable` first called here
- 4 │ extend type TestObject @nonRepeatable
+   │ 
+ 6 │ extend type TestObject @nonRepeatable
    │                        ───────┬──────  
    │                               ╰──────── directive `@nonRepeatable` called again here
 ───╯
 Error: non-repeatable directive nonRepeatable can only be used once per location
-   ╭─[0080_directive_is_unique_with_extensions.graphql:7:22]
+   ╭─[0080_directive_is_unique_with_extensions.graphql:9:22]
    │
- 6 │ scalar Scalar @nonRepeatable
+ 8 │ scalar Scalar @nonRepeatable
    │               ───────┬──────  
    │                      ╰──────── directive `@nonRepeatable` first called here
- 7 │ extend scalar Scalar @nonRepeatable @specifiedBy(url: "example.com")
+ 9 │ extend scalar Scalar @nonRepeatable @specifiedBy(url: "example.com")
    │                      ───────┬──────  
    │                             ╰──────── directive `@nonRepeatable` called again here
 ───╯
-Error: the type `Intf` is defined multiple times in the schema
-    ╭─[0080_directive_is_unique_with_extensions.graphql:12:11]
+Error: non-repeatable directive nonRepeatable can only be used once per location
+    ╭─[0080_directive_is_unique_with_extensions.graphql:14:23]
     │
-  9 │ interface Intf @nonRepeatable {
-    │           ──┬─  
-    │             ╰─── previous definition of `Intf` here
-    │ 
- 12 │ interface Intf @nonRepeatable
-    │           ──┬─  
-    │             ╰─── `Intf` redefined here
+ 11 │ interface Intf @nonRepeatable {
+    │                ───────┬──────  
+    │                       ╰──────── directive `@nonRepeatable` first called here
     │ 
-    │ Help: remove or rename one of the definitions, or use `extend`
+ 14 │ extend interface Intf @nonRepeatable
+    │                       ───────┬──────  
+    │                              ╰──────── directive `@nonRepeatable` called again here
 ────╯
 
diff --git a/crates/apollo-compiler/test_data/diagnostics/0081_directive_is_unique_type_system.txt b/crates/apollo-compiler/test_data/diagnostics/0081_directive_is_unique_type_system.txt
index f597257..bbc4c3e 100644
--- a/crates/apollo-compiler/test_data/diagnostics/0081_directive_is_unique_type_system.txt
+++ b/crates/apollo-compiler/test_data/diagnostics/0081_directive_is_unique_type_system.txt
@@ -16,6 +16,15 @@ Error: non-repeatable directive nonRepeatable can only be used once per location
    │                                         │        
    │                                         ╰──────── directive `@nonRepeatable` called again here
 ───╯
+Error: `Dummy` has no fields
+   ╭─[0081_directive_is_unique_type_system.graphql:5:1]
+   │
+ 5 │ type Dummy @nonRepeatable @nonRepeatable
+   │ ────────────────────┬───────────────────  
+   │                     ╰───────────────────── Dummy type defined here
+   │ 
+   │ Help: Define one or more fields on `Dummy`.
+───╯
 Error: non-repeatable directive nonRepeatable can only be used once per location
    ╭─[0081_directive_is_unique_type_system.graphql:5:27]
    │
@@ -25,6 +34,15 @@ Error: non-repeatable directive nonRepeatable can only be used once per location
    │                                  │        
    │                                  ╰──────── directive `@nonRepeatable` called again here
 ───╯
+Error: `TestInterface` has no fields
+   ╭─[0081_directive_is_unique_type_system.graphql:6:1]
+   │
+ 6 │ interface TestInterface @nonRepeatable @nonRepeatable
+   │ ──────────────────────────┬──────────────────────────  
+   │                           ╰──────────────────────────── TestInterface type defined here
+   │ 
+   │ Help: Define one or more fields on `TestInterface`.
+───╯
 Error: non-repeatable directive nonRepeatable can only be used once per location
    ╭─[0081_directive_is_unique_type_system.graphql:6:40]
    │
@@ -34,6 +52,15 @@ Error: non-repeatable directive nonRepeatable can only be used once per location
    │                                               │        
    │                                               ╰──────── directive `@nonRepeatable` called again here
 ───╯
+Error: `TestUnion` has no member types
+   ╭─[0081_directive_is_unique_type_system.graphql:7:1]
+   │
+ 7 │ union TestUnion @nonRepeatable @nonRepeatable
+   │ ──────────────────────┬──────────────────────  
+   │                       ╰──────────────────────── TestUnion type defined here
+   │ 
+   │ Help: Define one or more union member types on `TestUnion`.
+───╯
 Error: non-repeatable directive nonRepeatable can only be used once per location
    ╭─[0081_directive_is_unique_type_system.graphql:7:32]
    │
@@ -43,6 +70,15 @@ Error: non-repeatable directive nonRepeatable can only be used once per location
    │                                       │        
    │                                       ╰──────── directive `@nonRepeatable` called again here
 ───╯
+Error: `TestInput` has no input values
+   ╭─[0081_directive_is_unique_type_system.graphql:8:1]
+   │
+ 8 │ input TestInput @nonRepeatable @nonRepeatable
+   │ ──────────────────────┬──────────────────────  
+   │                       ╰──────────────────────── TestInput type defined here
+   │ 
+   │ Help: Define one or more input values on `TestInput`.
+───╯
 Error: non-repeatable directive nonRepeatable can only be used once per location
    ╭─[0081_directive_is_unique_type_system.graphql:8:32]
    │
diff --git a/crates/apollo-compiler/test_data/diagnostics/0114_interface_definition_with_missing_fields.graphql b/crates/apollo-compiler/test_data/diagnostics/0114_interface_definition_with_missing_fields.graphql
new file mode 100644
index 0000000..7421148
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0114_interface_definition_with_missing_fields.graphql
@@ -0,0 +1,8 @@
+directive @foo on INTERFACE
+type Query {
+    foo: Node
+}
+
+interface Node
+
+extend interface Node @foo
\ No newline at end of file
diff --git a/crates/apollo-compiler/test_data/diagnostics/0114_interface_definition_with_missing_fields.txt b/crates/apollo-compiler/test_data/diagnostics/0114_interface_definition_with_missing_fields.txt
new file mode 100644
index 0000000..466aa4e
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0114_interface_definition_with_missing_fields.txt
@@ -0,0 +1,14 @@
+Error: `Node` has no fields
+   ╭─[0114_interface_definition_with_missing_fields.graphql:6:1]
+   │
+ 6 │ interface Node
+   │ ───────┬──────  
+   │        ╰──────── Node type defined here
+   │ 
+ 8 │ extend interface Node @foo
+   │ ─────────────┬────────────  
+   │              ╰────────────── Node extension defined here
+   │ 
+   │ Help: Define one or more fields on `Node` or its type extensions.
+───╯
+
diff --git a/crates/apollo-compiler/test_data/diagnostics/0115_object_definition_with_missing_fields.graphql b/crates/apollo-compiler/test_data/diagnostics/0115_object_definition_with_missing_fields.graphql
new file mode 100644
index 0000000..ce21364
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0115_object_definition_with_missing_fields.graphql
@@ -0,0 +1,9 @@
+directive @foo on OBJECT
+directive @bar on OBJECT
+type Query {
+    foo: User
+}
+
+type User @foo
+
+extend type User @bar
diff --git a/crates/apollo-compiler/test_data/diagnostics/0115_object_definition_with_missing_fields.txt b/crates/apollo-compiler/test_data/diagnostics/0115_object_definition_with_missing_fields.txt
new file mode 100644
index 0000000..4972806
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0115_object_definition_with_missing_fields.txt
@@ -0,0 +1,14 @@
+Error: `User` has no fields
+   ╭─[0115_object_definition_with_missing_fields.graphql:7:1]
+   │
+ 7 │ type User @foo
+   │ ───────┬──────  
+   │        ╰──────── User type defined here
+   │ 
+ 9 │ extend type User @bar
+   │ ──────────┬──────────  
+   │           ╰──────────── User extension defined here
+   │ 
+   │ Help: Define one or more fields on `User` or its type extensions.
+───╯
+
diff --git a/crates/apollo-compiler/test_data/diagnostics/0116_enum_definition_with_missing_values.graphql b/crates/apollo-compiler/test_data/diagnostics/0116_enum_definition_with_missing_values.graphql
new file mode 100644
index 0000000..98f839d
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0116_enum_definition_with_missing_values.graphql
@@ -0,0 +1,8 @@
+directive @foo on ENUM
+type Query {
+    foo: WEEKDAY
+}
+
+enum WEEKDAY
+
+extend enum WEEKDAY @foo
\ No newline at end of file
diff --git a/crates/apollo-compiler/test_data/diagnostics/0116_enum_definition_with_missing_values.txt b/crates/apollo-compiler/test_data/diagnostics/0116_enum_definition_with_missing_values.txt
new file mode 100644
index 0000000..765cdd3
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0116_enum_definition_with_missing_values.txt
@@ -0,0 +1,14 @@
+Error: `WEEKDAY` has no enum values
+   ╭─[0116_enum_definition_with_missing_values.graphql:6:1]
+   │
+ 6 │ enum WEEKDAY
+   │ ──────┬─────  
+   │       ╰─────── WEEKDAY type defined here
+   │ 
+ 8 │ extend enum WEEKDAY @foo
+   │ ────────────┬───────────  
+   │             ╰───────────── WEEKDAY extension defined here
+   │ 
+   │ Help: Define one or more enum values on `WEEKDAY` or its type extensions.
+───╯
+
diff --git a/crates/apollo-compiler/test_data/diagnostics/0117_union_definition_with_missing_members.graphql b/crates/apollo-compiler/test_data/diagnostics/0117_union_definition_with_missing_members.graphql
new file mode 100644
index 0000000..4b3a0f7
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0117_union_definition_with_missing_members.graphql
@@ -0,0 +1,8 @@
+directive @foo on UNION
+type Query {
+    foo: Search
+}
+
+union Search
+
+extend union Search @foo
\ No newline at end of file
diff --git a/crates/apollo-compiler/test_data/diagnostics/0117_union_definition_with_missing_members.txt b/crates/apollo-compiler/test_data/diagnostics/0117_union_definition_with_missing_members.txt
new file mode 100644
index 0000000..cd436b0
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0117_union_definition_with_missing_members.txt
@@ -0,0 +1,14 @@
+Error: `Search` has no member types
+   ╭─[0117_union_definition_with_missing_members.graphql:6:1]
+   │
+ 6 │ union Search
+   │ ──────┬─────  
+   │       ╰─────── Search type defined here
+   │ 
+ 8 │ extend union Search @foo
+   │ ────────────┬───────────  
+   │             ╰───────────── Search extension defined here
+   │ 
+   │ Help: Define one or more union member types on `Search` or its type extensions.
+───╯
+
diff --git a/crates/apollo-compiler/test_data/diagnostics/0118_input_object_definition_with_missing_values.graphql b/crates/apollo-compiler/test_data/diagnostics/0118_input_object_definition_with_missing_values.graphql
new file mode 100644
index 0000000..7c46317
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0118_input_object_definition_with_missing_values.graphql
@@ -0,0 +1,8 @@
+directive @foo on INPUT_OBJECT
+type Query {
+    foo(id: In): String
+}
+
+input In
+
+extend input In @foo
\ No newline at end of file
diff --git a/crates/apollo-compiler/test_data/diagnostics/0118_input_object_definition_with_missing_values.txt b/crates/apollo-compiler/test_data/diagnostics/0118_input_object_definition_with_missing_values.txt
new file mode 100644
index 0000000..6a31ddc
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0118_input_object_definition_with_missing_values.txt
@@ -0,0 +1,14 @@
+Error: `In` has no input values
+   ╭─[0118_input_object_definition_with_missing_values.graphql:6:1]
+   │
+ 6 │ input In
+   │ ────┬───  
+   │     ╰───── In type defined here
+   │ 
+ 8 │ extend input In @foo
+   │ ──────────┬─────────  
+   │           ╰─────────── In extension defined here
+   │ 
+   │ Help: Define one or more input values on `In` or its type extensions.
+───╯
+
diff --git a/crates/apollo-compiler/test_data/ok/0115_interface_definition_with_extension_defines_field.graphql b/crates/apollo-compiler/test_data/ok/0115_interface_definition_with_extension_defines_field.graphql
new file mode 100644
index 0000000..a7334f5
--- /dev/null
+++ b/crates/apollo-compiler/test_data/ok/0115_interface_definition_with_extension_defines_field.graphql
@@ -0,0 +1,9 @@
+type Query {
+    foo: Node
+}
+
+interface Node
+
+extend interface Node {
+    bar: String
+}
diff --git a/crates/apollo-compiler/test_data/ok/0115_interface_definition_with_extension_defines_field.txt b/crates/apollo-compiler/test_data/ok/0115_interface_definition_with_extension_defines_field.txt
new file mode 100644
index 0000000..84b8a70
--- /dev/null
+++ b/crates/apollo-compiler/test_data/ok/0115_interface_definition_with_extension_defines_field.txt
@@ -0,0 +1,110 @@
+Schema {
+    sources: {
+        -1: SourceFile {
+            path: "built_in.graphql",
+            source_text: include_str!("built_in.graphql"),
+        },
+        41: SourceFile {
+            path: "0115_interface_definition_with_extension_defines_field.graphql",
+            source_text: "type Query {\n    foo: Node\n}\n\ninterface Node\n\nextend interface Node {\n    bar: String\n}\n",
+        },
+    },
+    schema_definition: SchemaDefinition {
+        description: None,
+        directives: [],
+        query: Some(
+            ComponentName {
+                origin: Definition,
+                name: "Query",
+            },
+        ),
+        mutation: None,
+        subscription: None,
+    },
+    directive_definitions: {
+        "skip": built_in_directive!("skip"),
+        "include": built_in_directive!("include"),
+        "deprecated": built_in_directive!("deprecated"),
+        "specifiedBy": built_in_directive!("specifiedBy"),
+    },
+    types: {
+        "__Schema": built_in_type!("__Schema"),
+        "__Type": built_in_type!("__Type"),
+        "__TypeKind": built_in_type!("__TypeKind"),
+        "__Field": built_in_type!("__Field"),
+        "__InputValue": built_in_type!("__InputValue"),
+        "__EnumValue": built_in_type!("__EnumValue"),
+        "__Directive": built_in_type!("__Directive"),
+        "__DirectiveLocation": built_in_type!("__DirectiveLocation"),
+        "Int": built_in_type!("Int"),
+        "Float": built_in_type!("Float"),
+        "String": built_in_type!("String"),
+        "Boolean": built_in_type!("Boolean"),
+        "ID": built_in_type!("ID"),
+        "Query": Object(
+            0..28 @41 ObjectType {
+                description: None,
+                name: "Query",
+                implements_interfaces: {},
+                directives: [],
+                fields: {
+                    "foo": Component {
+                        origin: Definition,
+                        node: 17..26 @41 FieldDefinition {
+                            description: None,
+                            name: "foo",
+                            arguments: [],
+                            ty: Named(
+                                "Node",
+                            ),
+                            directives: [],
+                        },
+                    },
+                },
+            },
+        ),
+        "Node": Interface(
+            30..44 @41 InterfaceType {
+                description: None,
+                name: "Node",
+                implements_interfaces: {},
+                directives: [],
+                fields: {
+                    "bar": Component {
+                        origin: Extension(
+                            ExtensionId {
+                                arc: Some(
+                                    46..87 @41,
+                                ),
+                            },
+                        ),
+                        node: 74..85 @41 FieldDefinition {
+                            description: None,
+                            name: "bar",
+                            arguments: [],
+                            ty: Named(
+                                "String",
+                            ),
+                            directives: [],
+                        },
+                    },
+                },
+            },
+        ),
+    },
+}
+ExecutableDocument {
+    sources: {
+        -1: SourceFile {
+            path: "built_in.graphql",
+            source_text: include_str!("built_in.graphql"),
+        },
+        41: SourceFile {
+            path: "0115_interface_definition_with_extension_defines_field.graphql",
+            source_text: "type Query {\n    foo: Node\n}\n\ninterface Node\n\nextend interface Node {\n    bar: String\n}\n",
+        },
+    },
+    anonymous_operation: None,
+    named_operations: {},
+    fragments: {},
+}
diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0080_directive_is_unique_with_extensions.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0080_directive_is_unique_with_extensions.graphql
index 29b3f3e..d237b92 100644
--- a/crates/apollo-compiler/test_data/serializer/diagnostics/0080_directive_is_unique_with_extensions.graphql
+++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0080_directive_is_unique_with_extensions.graphql
@@ -2,7 +2,9 @@ directive @nonRepeatable on OBJECT | SCALAR | INTERFACE
 
 extend type TestObject @nonRepeatable
 
-type TestObject @nonRepeatable
+type TestObject @nonRepeatable {
+  field: String
+}
 
 extend type TestObject @nonRepeatable
 
@@ -14,7 +16,7 @@ interface Intf @nonRepeatable {
   field: String
 }
 
-interface Intf @nonRepeatable
+extend interface Intf @nonRepeatable
 
 type Query {
   x: Int
diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0114_interface_definition_with_missing_fields.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0114_interface_definition_with_missing_fields.graphql
new file mode 100644
index 0000000..98b1145
--- /dev/null
+++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0114_interface_definition_with_missing_fields.graphql
@@ -0,0 +1,9 @@
+directive @foo on INTERFACE
+
+type Query {
+  foo: Node
+}
+
+interface Node
+
+extend interface Node @foo
diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0115_object_definition_with_missing_fields.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0115_object_definition_with_missing_fields.graphql
new file mode 100644
index 0000000..cce5965
--- /dev/null
+++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0115_object_definition_with_missing_fields.graphql
@@ -0,0 +1,11 @@
+directive @foo on OBJECT
+
+directive @bar on OBJECT
+
+type Query {
+  foo: User
+}
+
+type User @foo
+
+extend type User @bar
diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0116_enum_definition_with_missing_values.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0116_enum_definition_with_missing_values.graphql
new file mode 100644
index 0000000..483d1aa
--- /dev/null
+++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0116_enum_definition_with_missing_values.graphql
@@ -0,0 +1,9 @@
+directive @foo on ENUM
+
+type Query {
+  foo: WEEKDAY
+}
+
+enum WEEKDAY
+
+extend enum WEEKDAY @foo
diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0117_union_definition_with_missing_members.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0117_union_definition_with_missing_members.graphql
new file mode 100644
index 0000000..a2c9699
--- /dev/null
+++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0117_union_definition_with_missing_members.graphql
@@ -0,0 +1,9 @@
+directive @foo on UNION
+
+type Query {
+  foo: Search
+}
+
+union Search
+
+extend union Search @foo
diff --git a/crates/apollo-compiler/test_data/serializer/diagnostics/0118_input_object_definition_with_missing_values.graphql b/crates/apollo-compiler/test_data/serializer/diagnostics/0118_input_object_definition_with_missing_values.graphql
new file mode 100644
index 0000000..33077d3
--- /dev/null
+++ b/crates/apollo-compiler/test_data/serializer/diagnostics/0118_input_object_definition_with_missing_values.graphql
@@ -0,0 +1,9 @@
+directive @foo on INPUT_OBJECT
+
+type Query {
+  foo(id: In): String
+}
+
+input In
+
+extend input In @foo
diff --git a/crates/apollo-compiler/test_data/serializer/ok/0115_interface_definition_with_extension_defines_field.graphql b/crates/apollo-compiler/test_data/serializer/ok/0115_interface_definition_with_extension_defines_field.graphql
new file mode 100644
index 0000000..9a1766d
--- /dev/null
+++ b/crates/apollo-compiler/test_data/serializer/ok/0115_interface_definition_with_extension_defines_field.graphql
@@ -0,0 +1,9 @@
+type Query {
+  foo: Node
+}
+
+interface Node
+
+extend interface Node {
+  bar: String
+}
diff --git a/crates/apollo-compiler/tests/extensions.rs b/crates/apollo-compiler/tests/extensions.rs
index 4343c9f..897954f 100644
--- a/crates/apollo-compiler/tests/extensions.rs
+++ b/crates/apollo-compiler/tests/extensions.rs
@@ -4,7 +4,7 @@ use apollo_compiler::Schema;
 fn test_orphan_extensions() {
     let input = r#"
         extend schema @dir { query: Q }
-        extend type Obj @dir
+        extend type Obj @dir { foo: String }
         directive @dir on SCHEMA | OBJECT
         type Q { x: Int }
     "#;