-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Type extension field name uniqueness is checked only once #4076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
02bfb4a
0ebb109
ef0f329
71625e9
925208d
1001ed4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,9 +24,11 @@ | |
| import graphql.schema.idl.errors.TypeExtensionMissingBaseTypeError; | ||
| import graphql.util.FpKit; | ||
|
|
||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.function.Consumer; | ||
| import java.util.Set; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static graphql.schema.idl.SchemaTypeChecker.checkNamedUniqueness; | ||
|
|
@@ -80,18 +82,20 @@ private void checkObjectTypeExtensions(List<GraphQLError> errors, TypeDefinition | |
| checkNamedUniqueness(errors, directive.getArguments(), Argument::getName, | ||
| (argumentName, argument) -> new NonUniqueArgumentError(extension, fld, argumentName)))); | ||
|
|
||
| // | ||
| // fields must be unique within a type extension | ||
| forEachBut(extension, extensions, | ||
| otherTypeExt -> checkForFieldRedefinition(errors, otherTypeExt, otherTypeExt.getFieldDefinitions(), fieldDefinitions)); | ||
|
|
||
| // | ||
| // then check for field re-defs from the base type | ||
| ObjectTypeDefinition baseTypeDef = typeRegistry.getTypeOrNull(extension.getName(), ObjectTypeDefinition.class); | ||
| if (baseTypeDef != null) { | ||
| checkForFieldRedefinition(errors, extension, fieldDefinitions, baseTypeDef.getFieldDefinitions()); | ||
| } | ||
| }); | ||
|
|
||
| // fields must be unique within a type extension | ||
| checkForTypeExtensionFieldUniqueness( | ||
| errors, | ||
| extensions, | ||
| ObjectTypeDefinition::getFieldDefinitions | ||
| ); | ||
|
|
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new code is OUTSIDE the extentions loop and the new |
||
| } | ||
| ); | ||
| } | ||
|
|
@@ -125,18 +129,19 @@ private void checkInterfaceTypeExtensions(List<GraphQLError> errors, TypeDefinit | |
| checkNamedUniqueness(errors, directive.getArguments(), Argument::getName, | ||
| (argumentName, argument) -> new NonUniqueArgumentError(extension, fld, argumentName)))); | ||
|
|
||
| // | ||
| // fields must be unique within a type extension | ||
| forEachBut(extension, extensions, | ||
| otherTypeExt -> checkForFieldRedefinition(errors, otherTypeExt, otherTypeExt.getFieldDefinitions(), fieldDefinitions)); | ||
|
|
||
| // | ||
| // then check for field re-defs from the base type | ||
| InterfaceTypeDefinition baseTypeDef = typeRegistry.getTypeOrNull(extension.getName(), InterfaceTypeDefinition.class); | ||
| if (baseTypeDef != null) { | ||
| checkForFieldRedefinition(errors, extension, fieldDefinitions, baseTypeDef.getFieldDefinitions()); | ||
| } | ||
| }); | ||
| // fields must be unique within a type extension | ||
| checkForTypeExtensionFieldUniqueness( | ||
| errors, | ||
| extensions, | ||
| InterfaceTypeDefinition::getFieldDefinitions | ||
| ); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same but for interfaces |
||
| }); | ||
| } | ||
|
|
||
|
|
@@ -192,11 +197,6 @@ private void checkEnumTypeExtensions(List<GraphQLError> errors, TypeDefinitionRe | |
| checkNamedUniqueness(errors, enumValueDefinitions, EnumValueDefinition::getName, | ||
| (namedField, enumValue) -> new NonUniqueNameError(extension, enumValue)); | ||
|
|
||
| // | ||
| // enum values must be unique within a type extension | ||
| forEachBut(extension, extensions, | ||
| otherTypeExt -> checkForEnumValueRedefinition(errors, otherTypeExt, otherTypeExt.getEnumValueDefinitions(), enumValueDefinitions)); | ||
|
|
||
| // | ||
| // then check for field re-defs from the base type | ||
| EnumTypeDefinition baseTypeDef = typeRegistry.getTypeOrNull(extension.getName(), EnumTypeDefinition.class); | ||
|
|
@@ -206,7 +206,7 @@ private void checkEnumTypeExtensions(List<GraphQLError> errors, TypeDefinitionRe | |
|
|
||
| }); | ||
|
|
||
|
|
||
| checkForTypeExtensionEnumFieldUniqueness(errors, extensions, EnumTypeDefinition::getEnumValueDefinitions); | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -249,32 +249,32 @@ private void checkInputObjectTypeExtensions(List<GraphQLError> errors, TypeDefin | |
| checkNamedUniqueness(errors, directive.getArguments(), Argument::getName, | ||
| (argumentName, argument) -> new NonUniqueArgumentError(extension, fld, argumentName)))); | ||
| // | ||
| // fields must be unique within a type extension | ||
| forEachBut(extension, extensions, | ||
| otherTypeExt -> checkForInputValueRedefinition(errors, otherTypeExt, otherTypeExt.getInputValueDefinitions(), inputValueDefinitions)); | ||
|
|
||
| // | ||
| // then check for field re-defs from the base type | ||
| InputObjectTypeDefinition baseTypeDef = typeRegistry.getTypeOrNull(extension.getName(), InputObjectTypeDefinition.class); | ||
| if (baseTypeDef != null) { | ||
| checkForInputValueRedefinition(errors, extension, inputValueDefinitions, baseTypeDef.getInputValueDefinitions()); | ||
| } | ||
| }); | ||
| // | ||
| // fields must be unique within a type extension | ||
| checkForTypeExtensionInputFieldUniqueness( | ||
| errors, | ||
| extensions, | ||
| InputObjectTypeDefinition::getInputValueDefinitions | ||
| ); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. some but for input types |
||
|
|
||
| }); | ||
| } | ||
|
|
||
|
|
||
| private void checkTypeExtensionHasCorrespondingType(List<GraphQLError> errors, TypeDefinitionRegistry typeRegistry, String name, List<? extends TypeDefinition> extTypeList, Class<? extends TypeDefinition> targetClass) { | ||
| private void checkTypeExtensionHasCorrespondingType(List<GraphQLError> errors, TypeDefinitionRegistry typeRegistry, String name, List<? extends TypeDefinition<?>> extTypeList, Class<? extends TypeDefinition<?>> targetClass) { | ||
| TypeDefinition<?> extensionDefinition = extTypeList.get(0); | ||
| TypeDefinition<?> typeDefinition = typeRegistry.getTypeOrNull(TypeName.newTypeName().name(name).build(), targetClass); | ||
| if (typeDefinition == null) { | ||
| errors.add(new TypeExtensionMissingBaseTypeError(extensionDefinition)); | ||
| } | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
|
|
||
| private void checkForFieldRedefinition(List<GraphQLError> errors, TypeDefinition<?> typeDefinition, List<FieldDefinition> fieldDefinitions, List<FieldDefinition> referenceFieldDefinitions) { | ||
|
|
||
| Map<String, FieldDefinition> referenceMap = FpKit.getByName(referenceFieldDefinitions, FieldDefinition::getName, mergeFirst()); | ||
|
|
@@ -307,12 +307,57 @@ private void checkForEnumValueRedefinition(List<GraphQLError> errors, TypeDefini | |
| }); | ||
| } | ||
|
|
||
| private <T> void forEachBut(T butThisOne, List<T> list, Consumer<T> consumer) { | ||
| for (T t : list) { | ||
| if (t == butThisOne) { | ||
| continue; | ||
| private <T extends TypeDefinition<?>> void checkForTypeExtensionFieldUniqueness( | ||
| List<GraphQLError> errors, | ||
| List<T> extensions, | ||
| Function<T, List<FieldDefinition>> getFieldDefinitionsFunc | ||
| ) { | ||
| Set<String> seenFields = new HashSet<>(); | ||
|
|
||
| for (T extension : extensions) { | ||
| for (FieldDefinition field : getFieldDefinitionsFunc.apply(extension)) { | ||
| if (seenFields.contains(field.getName())) { | ||
| errors.add(new TypeExtensionFieldRedefinitionError(extension, field)); | ||
| } else { | ||
| seenFields.add(field.getName()); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private <T extends TypeDefinition<?>> void checkForTypeExtensionInputFieldUniqueness( | ||
| List<GraphQLError> errors, | ||
| List<T> extensions, | ||
| Function<T, List<InputValueDefinition>> getFieldDefinitionsFunc | ||
| ) { | ||
| Set<String> seenFields = new HashSet<>(); | ||
|
|
||
| for (T extension : extensions) { | ||
| for (InputValueDefinition field : getFieldDefinitionsFunc.apply(extension)) { | ||
| if (seenFields.contains(field.getName())) { | ||
| errors.add(new TypeExtensionFieldRedefinitionError(extension, field)); | ||
| } else { | ||
| seenFields.add(field.getName()); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private <T extends TypeDefinition<?>> void checkForTypeExtensionEnumFieldUniqueness( | ||
| List<GraphQLError> errors, | ||
| List<T> extensions, | ||
| Function<T, List<EnumValueDefinition>> getFieldDefinitionsFunc | ||
| ) { | ||
| Set<String> seenFields = new HashSet<>(); | ||
|
|
||
| for (T extension : extensions) { | ||
| for (EnumValueDefinition field : getFieldDefinitionsFunc.apply(extension)) { | ||
| if (seenFields.contains(field.getName())) { | ||
| errors.add(new TypeExtensionEnumValueRedefinitionError(extension, field)); | ||
| } else { | ||
| seenFields.add(field.getName()); | ||
| } | ||
| } | ||
| consumer.accept(t); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -943,7 +943,7 @@ class SchemaTypeCheckerTest extends Specification { | |
| expect: | ||
|
|
||
| !result.isEmpty() | ||
| result.size() == 4 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was curious what the extra error was: it is a field redefinition error, not a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine to have another error, it is correct anyway |
||
| result.size() == 5 | ||
| } | ||
|
|
||
| def "test that field args are unique"() { | ||
|
|
@@ -1824,4 +1824,120 @@ class SchemaTypeCheckerTest extends Specification { | |
| then: | ||
| errorContaining(result, "member type 'Bar' in Union 'DuplicateBar' is not unique. The member types of a Union type must be unique.") | ||
| } | ||
|
|
||
| def "how many errors do we get on type extension field redefinition"() { | ||
| def sdl = """ | ||
|
|
||
| type Query { | ||
| foo : Foo | ||
| } | ||
|
|
||
| type Foo { | ||
| foo : String | ||
| } | ||
|
|
||
| extend type Foo { | ||
| redefinedField : String | ||
| } | ||
|
|
||
| extend type Foo { | ||
| otherField1 : String | ||
| } | ||
|
|
||
| extend type Foo { | ||
| otherField2 : String | ||
| } | ||
|
|
||
| extend type Foo { | ||
| redefinedField : String | ||
| } | ||
|
|
||
| extend type Foo { | ||
| redefinedField : String | ||
| } | ||
|
|
||
| interface InterfaceType { | ||
| foo : String | ||
| } | ||
|
|
||
| extend interface InterfaceType { | ||
| redefinedInterfaceField : String | ||
| } | ||
|
|
||
| extend interface InterfaceType { | ||
| otherField1 : String | ||
| } | ||
|
|
||
| extend interface InterfaceType { | ||
| otherField2 : String | ||
| } | ||
|
|
||
| extend interface InterfaceType { | ||
| redefinedInterfaceField : String | ||
| } | ||
|
|
||
| extend interface InterfaceType { | ||
| redefinedInterfaceField : String | ||
| } | ||
|
|
||
| input Bar { | ||
| bar : String | ||
| } | ||
|
|
||
| extend input Bar { | ||
| redefinedInputField : String | ||
| } | ||
|
|
||
| extend input Bar { | ||
| otherField1 : String | ||
| } | ||
|
|
||
| extend input Bar { | ||
| otherField2 : String | ||
| } | ||
|
|
||
| extend input Bar { | ||
| redefinedInputField : String | ||
| } | ||
|
|
||
| extend input Bar { | ||
| redefinedInputField : String | ||
| } | ||
|
|
||
| enum Baz { | ||
| baz | ||
| } | ||
|
|
||
| extend enum Baz { | ||
| redefinedEnumValue | ||
| } | ||
|
|
||
| extend enum Baz { | ||
| otherField1 | ||
| } | ||
|
|
||
| extend enum Baz { | ||
| otherField2 | ||
| } | ||
|
|
||
| extend enum Baz { | ||
| redefinedEnumValue | ||
| } | ||
|
|
||
| extend enum Baz { | ||
| redefinedEnumValue | ||
| } | ||
|
|
||
| """ | ||
|
|
||
| when: | ||
| def result = check(sdl) | ||
|
|
||
| then: | ||
| result.size() == 8 | ||
| errorContaining(result, "'Foo' extension type [@n:n] tried to redefine field 'redefinedField' [@n:n]") | ||
| errorContaining(result, "'InterfaceType' extension type [@n:n] tried to redefine field 'redefinedInterfaceField' [@n:n]") | ||
| errorContaining(result, "'Bar' extension type [@n:n] tried to redefine field 'redefinedInputField' [@n:n]") | ||
| errorContaining(result, "'Baz' extension type [@n:n] tried to redefine enum value 'redefinedEnumValue' [@n:n]") | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code here is inside a loop already - so
This created a N*M loop