diff --git a/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/resolver_on_interface_does_not_pass_schema_validation.invalid.expected b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/resolver_on_interface_does_not_pass_schema_validation.invalid.expected index 940ef9fa2e39e..d6b06a092a12a 100644 --- a/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/resolver_on_interface_does_not_pass_schema_validation.invalid.expected +++ b/compiler/crates/relay-compiler/tests/relay_compiler_integration/fixtures/resolver_on_interface_does_not_pass_schema_validation.invalid.expected @@ -46,7 +46,7 @@ interface IPerson { name: String } ==================================== OUTPUT =================================== -✖︎ Interface field 'IPerson.name' expected but 'Admin' does not provide it. +✖︎ Interface field 'IPerson.name' expected but type 'Admin' does not provide it. AdminTypeResolvers.js:2:19 1 │ * diff --git a/compiler/crates/schema-validate/src/errors.rs b/compiler/crates/schema-validate/src/errors.rs index e2dc62dbe1d7b..2e0bcf996008c 100644 --- a/compiler/crates/schema-validate/src/errors.rs +++ b/compiler/crates/schema-validate/src/errors.rs @@ -44,8 +44,8 @@ pub enum SchemaValidationError { #[error("Type '{0}' can only implement '{1}' once.")] DuplicateInterfaceImplementation(StringKey, InterfaceName), - #[error("Interface field '{0}.{1}' expected but '{2}' does not provide it.")] - InterfaceFieldNotProvided(InterfaceName, StringKey, StringKey), + #[error("Interface field '{0}.{1}' expected but {2} '{3}' does not provide it.")] + InterfaceFieldNotProvided(InterfaceName, StringKey, StringKey, StringKey), #[error("Interface field '{0}.{1}' expects type '{2}' but '{3}.{1}' is of type '{4}'.")] NotASubType(InterfaceName, StringKey, String, StringKey, String), diff --git a/compiler/crates/schema-validate/src/lib.rs b/compiler/crates/schema-validate/src/lib.rs index 8cdec7a70f23c..dc37dc1774c1b 100644 --- a/compiler/crates/schema-validate/src/lib.rs +++ b/compiler/crates/schema-validate/src/lib.rs @@ -389,6 +389,7 @@ impl<'schema> ValidationContext<'schema> { SchemaValidationError::InterfaceFieldNotProvided( interface.name.item, field_name, + type_.type_kind(), typename, ), *type_.location(), diff --git a/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_implements_interface.expected b/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_implements_interface.expected index 7f3a0da8016c4..ae4039d2805b2 100644 --- a/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_implements_interface.expected +++ b/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_implements_interface.expected @@ -1,4 +1,6 @@ ==================================== INPUT ==================================== +# Reports full type, not just inner type + interface Node { id: ID! } @@ -7,22 +9,118 @@ type Pet implements Node { id: ID # <--- Missing ! } +# Subtypes for fields are allowed + +interface InterfaceA { + some_field: String +} + +type TypeA implements InterfaceA { + some_field: String! # More specific type of String! should be allowed +} + +# Checks multiple interfaces + +interface InterfaceB { + some_field: String +} + +interface InterfaceC { + another_field: String +} + +type TypeB implements InterfaceB & InterfaceC { + some_field: String + # Oops! Does not implement InterfaceC's field! +} + +# Checks interface implements interface + +interface InterfaceD { + some_field: String +} + +interface InterfaceE implements InterfaceD { + # Oops! Does not implement InterfaceD's field + another_field: String +} + +# Checks multi-dimensional lists + +interface InterfaceF { + some_field: [[[String]]] +} + +type TypeC implements InterfaceF { + some_field: [[[Int]]] # Oops! Should be String in there +} + +# Required for global validation + type Query { node: Node } ==================================== OUTPUT =================================== ✖︎ Interface field 'Node.id' expects type 'ID!' but 'Pet.id' is of type 'ID'. - validate_implements_interface.graphql:6:3 - 5 │ type Pet implements Node { - 6 │ id: ID # <--- Missing ! + validate_implements_interface.graphql:8:3 + 7 │ type Pet implements Node { + 8 │ id: ID # <--- Missing ! │ ^^ - 7 │ } + 9 │ } ℹ︎ The interface field is defined here: - validate_implements_interface.graphql:2:3 - 1 │ interface Node { - 2 │ id: ID! + validate_implements_interface.graphql:4:3 + 3 │ interface Node { + 4 │ id: ID! │ ^^ - 3 │ } + 5 │ } + +✖︎ Interface field 'InterfaceC.another_field' expected but type 'TypeB' does not provide it. + + validate_implements_interface.graphql:31:6 + 30 │ + 31 │ type TypeB implements InterfaceB & InterfaceC { + │ ^^^^^ + 32 │ some_field: String + + ℹ︎ The interface field is defined here: + + validate_implements_interface.graphql:28:3 + 27 │ interface InterfaceC { + 28 │ another_field: String + │ ^^^^^^^^^^^^^ + 29 │ } + +✖︎ Interface field 'InterfaceD.some_field' expected but interface 'InterfaceE' does not provide it. + + validate_implements_interface.graphql:42:11 + 41 │ + 42 │ interface InterfaceE implements InterfaceD { + │ ^^^^^^^^^^ + 43 │ # Oops! Does not implement InterfaceD's field + + ℹ︎ The interface field is defined here: + + validate_implements_interface.graphql:39:3 + 38 │ interface InterfaceD { + 39 │ some_field: String + │ ^^^^^^^^^^ + 40 │ } + +✖︎ Interface field 'InterfaceF.some_field' expects type '[[[String]]]' but 'TypeC.some_field' is of type '[[[Int]]]'. + + validate_implements_interface.graphql:54:3 + 53 │ type TypeC implements InterfaceF { + 54 │ some_field: [[[Int]]] # Oops! Should be String in there + │ ^^^^^^^^^^ + 55 │ } + + ℹ︎ The interface field is defined here: + + validate_implements_interface.graphql:50:3 + 49 │ interface InterfaceF { + 50 │ some_field: [[[String]]] + │ ^^^^^^^^^^ + 51 │ } diff --git a/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_implements_interface.graphql b/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_implements_interface.graphql index 5bf169c0f3173..955cd77be3e5d 100644 --- a/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_implements_interface.graphql +++ b/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_implements_interface.graphql @@ -1,3 +1,5 @@ +# Reports full type, not just inner type + interface Node { id: ID! } @@ -6,6 +8,54 @@ type Pet implements Node { id: ID # <--- Missing ! } +# Subtypes for fields are allowed + +interface InterfaceA { + some_field: String +} + +type TypeA implements InterfaceA { + some_field: String! # More specific type of String! should be allowed +} + +# Checks multiple interfaces + +interface InterfaceB { + some_field: String +} + +interface InterfaceC { + another_field: String +} + +type TypeB implements InterfaceB & InterfaceC { + some_field: String + # Oops! Does not implement InterfaceC's field! +} + +# Checks interface implements interface + +interface InterfaceD { + some_field: String +} + +interface InterfaceE implements InterfaceD { + # Oops! Does not implement InterfaceD's field + another_field: String +} + +# Checks multi-dimensional lists + +interface InterfaceF { + some_field: [[[String]]] +} + +type TypeC implements InterfaceF { + some_field: [[[Int]]] # Oops! Should be String in there +} + +# Required for global validation + type Query { node: Node } diff --git a/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_object.expected b/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_object.expected index 29ce6a3178c8b..a69e7566b894d 100644 --- a/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_object.expected +++ b/compiler/crates/schema-validate/tests/validate_schema/fixtures/validate_object.expected @@ -95,7 +95,7 @@ type Fur { │ ^^^^^^^ 4 │ pet: Canine -✖︎ Interface field 'Canine.name' expected but 'Pet' does not provide it. +✖︎ Interface field 'Canine.name' expected but type 'Pet' does not provide it. validate_object.graphql:13:6 12 │ diff --git a/compiler/crates/schema/src/definitions.rs b/compiler/crates/schema/src/definitions.rs index 5d625f2e44841..297d25a7ee6bc 100644 --- a/compiler/crates/schema/src/definitions.rs +++ b/compiler/crates/schema/src/definitions.rs @@ -12,6 +12,7 @@ use std::fmt; use std::hash::Hash; use std::slice::Iter; +use ::intern::string_key::Intern; use ::intern::string_key::StringKey; use common::ArgumentName; use common::DirectiveName; @@ -28,7 +29,7 @@ use common::WithLocation; use graphql_syntax::ConstantValue; use graphql_syntax::DirectiveLocation; pub use interface::*; -use intern::string_key::Intern; +use intern::intern; use lazy_static::lazy_static; use crate::Schema; @@ -626,12 +627,17 @@ impl IntoIterator for ArgumentDefinitions { } pub trait TypeWithFields { + fn type_kind(&self) -> StringKey; fn fields(&self) -> &Vec; fn interfaces(&self) -> &Vec; fn location(&self) -> &Location; } impl TypeWithFields for Interface { + fn type_kind(&self) -> StringKey { + intern!("interface") + } + fn fields(&self) -> &Vec { &self.fields } @@ -646,6 +652,9 @@ impl TypeWithFields for Interface { } impl TypeWithFields for Object { + fn type_kind(&self) -> StringKey { + intern!("type") + } fn fields(&self) -> &Vec { &self.fields }