Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 76 additions & 31 deletions src/main/java/graphql/schema/idl/SchemaTypeExtensionsChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));

//
Copy link
Member Author

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

  • for every type extentions
    • do this check where we loop every type extension

This created a N*M loop

// 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
);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new code is OUTSIDE the extentions loop and the new checkForTypeExtensionFieldUniqueness loops extensions only once for field unique names so its order N

}
);
}
Expand Down Expand Up @@ -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
);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same but for interfaces

});
}

Expand Down Expand Up @@ -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);
Expand All @@ -206,7 +206,7 @@ private void checkEnumTypeExtensions(List<GraphQLError> errors, TypeDefinitionRe

});


checkForTypeExtensionEnumFieldUniqueness(errors, extensions, EnumTypeDefinition::getEnumValueDefinitions);
});
}

Expand Down Expand Up @@ -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
);
Copy link
Member Author

Choose a reason for hiding this comment

The 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());
Expand Down Expand Up @@ -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);
}
}
}
118 changes: 117 additions & 1 deletion src/test/groovy/graphql/schema/idl/SchemaTypeCheckerTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ class SchemaTypeCheckerTest extends Specification {
expect:

!result.isEmpty()
result.size() == 4
Copy link
Member

Choose a reason for hiding this comment

The 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 NonUniqueNameError like the others in this list.

'Query' extension type [@8:13] tried to redefine field 'fieldB' [@10:17]

Copy link
Member

Choose a reason for hiding this comment

The 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"() {
Expand Down Expand Up @@ -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]")
}
}