Skip to content

Commit bdb3568

Browse files
committed
graphql-java#410 - Added interface checking on types at IDL level
1 parent a62601f commit bdb3568

7 files changed

+340
-10
lines changed

src/main/java/graphql/schema/idl/SchemaTypeChecker.java

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
import graphql.language.TypeExtensionDefinition;
1515
import graphql.language.TypeName;
1616
import graphql.language.UnionTypeDefinition;
17+
import graphql.schema.idl.errors.InterfaceFieldArgumentRedefinitionError;
18+
import graphql.schema.idl.errors.InterfaceFieldRedefinitionError;
19+
import graphql.schema.idl.errors.MissingInterfaceFieldArgumentsError;
20+
import graphql.schema.idl.errors.MissingInterfaceFieldError;
1721
import graphql.schema.idl.errors.MissingInterfaceTypeError;
1822
import graphql.schema.idl.errors.MissingScalarImplementationError;
1923
import graphql.schema.idl.errors.MissingTypeError;
@@ -48,6 +52,8 @@ public List<GraphQLError> checkTypeRegistry(TypeDefinitionRegistry typeRegistry,
4852
checkTypeExtensionsHaveCorrespondingType(errors, typeRegistry);
4953
checkTypeExtensionsFieldRedefinition(errors, typeRegistry);
5054

55+
checkInterfacesAreImplemented(errors, typeRegistry);
56+
5157
checkSchemaInvariants(errors, typeRegistry);
5258

5359
checkScalarImplementationsArePresent(errors, typeRegistry, wiring);
@@ -136,7 +142,6 @@ private void checkForMissingTypes(List<GraphQLError> errors, TypeDefinitionRegis
136142
});
137143
}
138144

139-
140145
private void checkScalarImplementationsArePresent(List<GraphQLError> errors, TypeDefinitionRegistry typeRegistry, RuntimeWiring wiring) {
141146
typeRegistry.scalars().keySet().forEach(scalarName -> {
142147
if (!wiring.getScalars().containsKey(scalarName)) {
@@ -145,6 +150,7 @@ private void checkScalarImplementationsArePresent(List<GraphQLError> errors, Typ
145150
});
146151
}
147152

153+
148154
private void checkTypeResolversArePresent(List<GraphQLError> errors, TypeDefinitionRegistry typeRegistry, RuntimeWiring wiring) {
149155

150156
Consumer<TypeDefinition> checkForResolver = typeDef -> {
@@ -159,7 +165,6 @@ private void checkTypeResolversArePresent(List<GraphQLError> errors, TypeDefinit
159165

160166
}
161167

162-
163168
private void checkFieldTypesPresent(TypeDefinitionRegistry typeRegistry, List<GraphQLError> errors, TypeDefinition typeDefinition, List<FieldDefinition> fields) {
164169
List<Type> fieldTypes = fields.stream().map(FieldDefinition::getType).collect(Collectors.toList());
165170
fieldTypes.forEach(checkTypeExists("field", typeRegistry, errors, typeDefinition));
@@ -175,6 +180,7 @@ private void checkFieldTypesPresent(TypeDefinitionRegistry typeRegistry, List<Gr
175180
fieldInputValues.forEach(checkTypeExists("field input", typeRegistry, errors, typeDefinition));
176181
}
177182

183+
178184
private Consumer<Type> checkTypeExists(String typeOfType, TypeDefinitionRegistry typeRegistry, List<GraphQLError> errors, TypeDefinition typeDefinition) {
179185
return t -> {
180186
TypeName unwrapped = TypeInfo.typeInfo(t).getTypeName();
@@ -197,6 +203,76 @@ private Consumer<? super Type> checkInterfaceTypeExists(TypeDefinitionRegistry t
197203
};
198204
}
199205

206+
private void checkInterfacesAreImplemented(List<GraphQLError> errors, TypeDefinitionRegistry typeRegistry) {
207+
Map<String, TypeDefinition> typesMap = typeRegistry.types();
208+
209+
// objects
210+
List<ObjectTypeDefinition> objectTypes = filterTo(typesMap, ObjectTypeDefinition.class);
211+
objectTypes.forEach(objectType -> {
212+
List<Type> implementsTypes = objectType.getImplements();
213+
implementsTypes.forEach(checkInterfaceIsImplemented("object", typeRegistry, errors, objectType));
214+
});
215+
216+
Map<String, List<TypeExtensionDefinition>> typeExtensions = typeRegistry.typeExtensions();
217+
typeExtensions.values().forEach(extList -> extList.forEach(typeExtension -> {
218+
List<Type> implementsTypes = typeExtension.getImplements();
219+
implementsTypes.forEach(checkInterfaceIsImplemented("extension", typeRegistry, errors, typeExtension));
220+
}));
221+
}
222+
223+
private Consumer<? super Type> checkInterfaceIsImplemented(String typeOfType, TypeDefinitionRegistry typeRegistry, List<GraphQLError> errors, ObjectTypeDefinition objectTypeDef) {
224+
return t -> {
225+
TypeInfo typeInfo = TypeInfo.typeInfo(t);
226+
TypeName unwrapped = typeInfo.getTypeName();
227+
Optional<TypeDefinition> type = typeRegistry.getType(unwrapped);
228+
// previous checks handle the missing case and wrong type case
229+
if (type.isPresent() && type.get() instanceof InterfaceTypeDefinition) {
230+
InterfaceTypeDefinition interfaceTypeDef = (InterfaceTypeDefinition) type.get();
231+
232+
Map<String, FieldDefinition> objectFields = objectTypeDef.getFieldDefinitions().stream()
233+
.collect(Collectors.toMap(
234+
FieldDefinition::getName, Function.identity()
235+
));
236+
237+
interfaceTypeDef.getFieldDefinitions().forEach(interfaceFieldDef -> {
238+
FieldDefinition objectFieldDef = objectFields.get(interfaceFieldDef.getName());
239+
if (objectFieldDef == null) {
240+
errors.add(new MissingInterfaceFieldError(typeOfType, objectTypeDef, interfaceTypeDef, interfaceFieldDef));
241+
} else {
242+
String interfaceFieldType = AstPrinter.printAst(interfaceFieldDef.getType());
243+
String objectFieldType = AstPrinter.printAst(objectFieldDef.getType());
244+
if (!interfaceFieldType.equals(objectFieldType)) {
245+
errors.add(new InterfaceFieldRedefinitionError(typeOfType, objectTypeDef, interfaceTypeDef, objectFieldDef, objectFieldType, interfaceFieldType));
246+
}
247+
248+
// look at arguments
249+
List<InputValueDefinition> objectArgs = objectFieldDef.getInputValueDefinitions();
250+
List<InputValueDefinition> interfaceArgs = interfaceFieldDef.getInputValueDefinitions();
251+
if (objectArgs.size() != interfaceArgs.size()) {
252+
errors.add(new MissingInterfaceFieldArgumentsError(typeOfType, objectTypeDef, interfaceTypeDef, objectFieldDef));
253+
} else {
254+
checkArgumentConsistency(typeOfType, objectTypeDef, interfaceTypeDef, objectFieldDef, interfaceFieldDef, errors);
255+
}
256+
}
257+
});
258+
}
259+
};
260+
}
261+
262+
private void checkArgumentConsistency(String typeOfType, ObjectTypeDefinition objectTypeDef, InterfaceTypeDefinition interfaceTypeDef, FieldDefinition objectFieldDef, FieldDefinition interfaceFieldDef, List<GraphQLError> errors) {
263+
List<InputValueDefinition> objectArgs = objectFieldDef.getInputValueDefinitions();
264+
List<InputValueDefinition> interfaceArgs = interfaceFieldDef.getInputValueDefinitions();
265+
for (int i = 0; i < interfaceArgs.size(); i++) {
266+
InputValueDefinition interfaceArg = interfaceArgs.get(i);
267+
InputValueDefinition objectArg = objectArgs.get(i);
268+
String interfaceArgStr = AstPrinter.printAst(interfaceArg);
269+
String objectArgStr = AstPrinter.printAst(objectArg);
270+
if (!interfaceArgStr.equals(objectArgStr)) {
271+
errors.add(new InterfaceFieldArgumentRedefinitionError(typeOfType, objectTypeDef, interfaceTypeDef, objectFieldDef, objectArgStr, interfaceArgStr));
272+
}
273+
}
274+
}
275+
200276
/*
201277
A type can re-define a field if its actual the same type, but if they make 'fieldA : String' into
202278
'fieldA : Int' then we cant handle that. Even 'fieldA : String' to 'fieldA: String!' is tough to handle

src/main/java/graphql/schema/idl/errors/BaseError.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ public ErrorType getErrorType() {
3636

3737
@Override
3838
public String toString() {
39-
return "BaseError{" +
40-
"msg='" + getMessage() + '\'' +
41-
", node=" + node +
42-
'}';
39+
return getMessage();
4340
}
4441
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package graphql.schema.idl.errors;
2+
3+
import graphql.language.FieldDefinition;
4+
import graphql.language.InterfaceTypeDefinition;
5+
import graphql.language.ObjectTypeDefinition;
6+
7+
import static java.lang.String.format;
8+
9+
10+
public class InterfaceFieldArgumentRedefinitionError extends BaseError {
11+
public InterfaceFieldArgumentRedefinitionError(String typeOfType, ObjectTypeDefinition objectTypeDef, InterfaceTypeDefinition interfaceTypeDef, FieldDefinition objectFieldDef, String objectArgStr, String interfaceArgStr) {
12+
super(objectTypeDef, format("The %s type '%s' %s has tried to redefine field '%s' arguments defined via interface '%s' %s from '%s' to '%s",
13+
typeOfType, objectTypeDef.getName(), lineCol(objectTypeDef), objectFieldDef.getName(), interfaceTypeDef.getName(), lineCol(interfaceTypeDef), interfaceArgStr, objectArgStr));
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package graphql.schema.idl.errors;
2+
3+
import graphql.language.FieldDefinition;
4+
import graphql.language.InterfaceTypeDefinition;
5+
import graphql.language.ObjectTypeDefinition;
6+
7+
import static java.lang.String.format;
8+
9+
public class InterfaceFieldRedefinitionError extends BaseError {
10+
public InterfaceFieldRedefinitionError(String typeOfType, ObjectTypeDefinition objectType, InterfaceTypeDefinition interfaceTypeDef, FieldDefinition objectFieldDef, String objectFieldType, String interfaceFieldType) {
11+
super(objectType, format("The %s type '%s' %s has tried to redefine field '%s' defined via interface '%s' %s from '%s' to '%s",
12+
typeOfType, objectType.getName(), lineCol(objectType), objectFieldDef.getName(), interfaceTypeDef.getName(), lineCol(interfaceTypeDef), interfaceFieldType, objectFieldType));
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package graphql.schema.idl.errors;
2+
3+
import graphql.language.FieldDefinition;
4+
import graphql.language.InterfaceTypeDefinition;
5+
import graphql.language.ObjectTypeDefinition;
6+
7+
import static java.lang.String.format;
8+
9+
public class MissingInterfaceFieldArgumentsError extends BaseError {
10+
public MissingInterfaceFieldArgumentsError(String typeOfType, ObjectTypeDefinition objectTypeDef, InterfaceTypeDefinition interfaceTypeDef, FieldDefinition objectFieldDef) {
11+
super(objectTypeDef, format("The %s type '%s' %s field '%s' does not have the same number of arguments as specified via interface '%s' %s",
12+
typeOfType, objectTypeDef.getName(), lineCol(objectTypeDef), objectFieldDef.getName(), interfaceTypeDef.getName(), lineCol(interfaceTypeDef)));
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package graphql.schema.idl.errors;
2+
3+
import graphql.language.FieldDefinition;
4+
import graphql.language.InterfaceTypeDefinition;
5+
import graphql.language.ObjectTypeDefinition;
6+
7+
import static java.lang.String.format;
8+
9+
public class MissingInterfaceFieldError extends BaseError {
10+
public MissingInterfaceFieldError(String typeOfType, ObjectTypeDefinition objectType, InterfaceTypeDefinition interfaceTypeDef, FieldDefinition interfaceFieldDef) {
11+
super(objectType, format("The %s type '%s' %s does not have a field '%s' required via interface '%s' %s",
12+
typeOfType, objectType.getName(), lineCol(objectType), interfaceFieldDef.getName(), interfaceTypeDef.getName(), lineCol(interfaceTypeDef)));
13+
}
14+
}

0 commit comments

Comments
 (0)