Repository: apollographql/apollo-rs (rust)

Issue:
validation: input object extensions
* [ ] The named type must already be defined 
* [ ] the already defined type must be a Input Object type.
* [ ] All fields of an Input Object type extension must have unique names.
* [ ] All fields of an Input Object type extension must not already be a field of the original Input Object.
* [ ] Any non-repeatable directives provided must not already apply to the original Input Object type.

[spec](https://spec.graphql.org/October2021/#sec-Input-Object-Extensions.Type-Validation)

Pull request description:
feat(compiler): validate input object extensions, closes #480
Currently duplicating `collect_nodes` in every file, will consolidate after the fact.

Patch:
diff --git a/crates/apollo-compiler/src/validation/input_object.rs b/crates/apollo-compiler/src/validation/input_object.rs
index 6684a9c..8d84199 100644
--- a/crates/apollo-compiler/src/validation/input_object.rs
+++ b/crates/apollo-compiler/src/validation/input_object.rs
@@ -77,6 +77,18 @@ pub fn validate_input_object_definitions(db: &dyn ValidationDatabase) -> Vec<Apo
     diagnostics
 }
 
+fn collect_nodes<'a, Item: Clone, Ext>(
+    base: &'a [Item],
+    extensions: &'a [Arc<Ext>],
+    method: impl Fn(&'a Ext) -> &'a [Item],
+) -> Vec<Item> {
+    let mut nodes = base.to_vec();
+    for ext in extensions {
+        nodes.extend(method(ext).iter().cloned());
+    }
+    nodes
+}
+
 pub fn validate_input_object_definition(
     db: &dyn ValidationDatabase,
     input_obj: Arc<hir::InputObjectTypeDefinition>,
@@ -109,8 +121,13 @@ pub fn validate_input_object_definition(
     // Fields in an Input Object Definition must be unique
     //
     // Returns Unique Definition error.
+    let fields = collect_nodes(
+        input_obj.input_fields_definition.as_ref(),
+        input_obj.extensions(),
+        hir::InputObjectTypeExtension::input_fields_definition,
+    );
     diagnostics.extend(db.validate_input_values(
-        input_obj.input_fields_definition.clone(),
+        Arc::new(fields),
         hir::DirectiveLocation::InputFieldDefinition,
     ));
 


Test patch:
diff --git a/crates/apollo-compiler/test_data/diagnostics/0099_input_object_extensions.graphql b/crates/apollo-compiler/test_data/diagnostics/0099_input_object_extensions.graphql
new file mode 100644
index 0000000..cf69532
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0099_input_object_extensions.graphql
@@ -0,0 +1,12 @@
+input UniqueNames {
+  field: String
+}
+extend input UniqueNames {
+  field: String
+}
+
+directive @nonRepeatable on INPUT_OBJECT
+input UniqueDirective @nonRepeatable {
+  field: String
+}
+extend input UniqueDirective @nonRepeatable
diff --git a/crates/apollo-compiler/test_data/diagnostics/0099_input_object_extensions.txt b/crates/apollo-compiler/test_data/diagnostics/0099_input_object_extensions.txt
new file mode 100644
index 0000000..6a9e37f
--- /dev/null
+++ b/crates/apollo-compiler/test_data/diagnostics/0099_input_object_extensions.txt
@@ -0,0 +1,110 @@
+[
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            96: "0099_input_object_extensions.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 96,
+            },
+            offset: 22,
+            length: 13,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 96,
+                    },
+                    offset: 22,
+                    length: 13,
+                },
+                text: "previous definition of `field` here",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 96,
+                    },
+                    offset: 67,
+                    length: 13,
+                },
+                text: "`field` redefined here",
+            },
+        ],
+        help: Some(
+            "`field` field must only be defined once in this input object definition.",
+        ),
+        data: UniqueInputValue {
+            name: "field",
+            original_value: DiagnosticLocation {
+                file_id: FileId {
+                    id: 96,
+                },
+                offset: 22,
+                length: 13,
+            },
+            redefined_value: DiagnosticLocation {
+                file_id: FileId {
+                    id: 96,
+                },
+                offset: 67,
+                length: 13,
+            },
+        },
+    },
+    ApolloDiagnostic {
+        cache: {
+            0: "built_in_types.graphql",
+            96: "0099_input_object_extensions.graphql",
+        },
+        location: DiagnosticLocation {
+            file_id: FileId {
+                id: 96,
+            },
+            offset: 211,
+            length: 14,
+        },
+        labels: [
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 96,
+                    },
+                    offset: 147,
+                    length: 14,
+                },
+                text: "directive nonRepeatable first called here",
+            },
+            Label {
+                location: DiagnosticLocation {
+                    file_id: FileId {
+                        id: 96,
+                    },
+                    offset: 211,
+                    length: 14,
+                },
+                text: "directive nonRepeatable called again here",
+            },
+        ],
+        help: None,
+        data: UniqueDirective {
+            name: "nonRepeatable",
+            original_call: DiagnosticLocation {
+                file_id: FileId {
+                    id: 96,
+                },
+                offset: 147,
+                length: 14,
+            },
+            conflicting_call: DiagnosticLocation {
+                file_id: FileId {
+                    id: 96,
+                },
+                offset: 211,
+                length: 14,
+            },
+        },
+    },
+]