package graphql.schema.validation;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLType;
import java.util.List;
import java.util.Objects;
import static graphql.schema.GraphQLTypeUtil.getUnwrappedTypeName;
import static graphql.schema.validation.SchemaValidationErrorType.ObjectDoesNotImplementItsInterfaces;
import static java.lang.String.format;
/**
* Schema validation rule ensuring object types have all the fields that they need to implement the interfaces
* they say they implement
*/
public class ObjectsImplementInterfaces implements SchemaValidationRule {
@Override
public void check(GraphQLFieldDefinition fieldDef, SchemaValidationErrorCollector validationErrorCollector) {
}
@Override
public void check(GraphQLType type, SchemaValidationErrorCollector validationErrorCollector) {
if (type instanceof GraphQLObjectType) {
check((GraphQLObjectType) type, validationErrorCollector);
}
}
// visible for testing
private void check(GraphQLObjectType objectTyoe, SchemaValidationErrorCollector validationErrorCollector) {
List<GraphQLOutputType> interfaces = objectTyoe.getInterfaces();
interfaces.forEach(interfaceType -> {
// we have resolved the interfaces at this point and hence the cast is ok
checkObjectImplementsInterface(objectTyoe, (GraphQLInterfaceType) interfaceType, validationErrorCollector);
});
}
private void checkObjectImplementsInterface(GraphQLObjectType objectTyoe, GraphQLInterfaceType interfaceType, SchemaValidationErrorCollector validationErrorCollector) {
List<GraphQLFieldDefinition> fieldDefinitions = interfaceType.getFieldDefinitions();
for (GraphQLFieldDefinition interfaceFieldDef : fieldDefinitions) {
GraphQLFieldDefinition objectFieldDef = objectTyoe.getFieldDefinition(interfaceFieldDef.getName());
if (objectFieldDef == null) {
validationErrorCollector.addError(
error(format("object type '%s' does not implement interface '%s' because field '%s' is missing",
objectTyoe.getName(), interfaceType.getName(), interfaceFieldDef.getName())));
} else {
checkFieldTypeEquivalence(objectTyoe, interfaceType, validationErrorCollector, interfaceFieldDef, objectFieldDef);
}
}
}
private void checkFieldTypeEquivalence(GraphQLObjectType objectTyoe, GraphQLInterfaceType interfaceType, SchemaValidationErrorCollector validationErrorCollector, GraphQLFieldDefinition interfaceFieldDef, GraphQLFieldDefinition objectFieldDef) {
// the reference implementation has a full but complicated abstract type equivalence check
// this is not that but we can add that later. It does cover the major cases however
String interfaceFieldDefStr = getUnwrappedTypeName(interfaceFieldDef.getType());
String objectFieldDefStr = getUnwrappedTypeName(objectFieldDef.getType());
if (!interfaceFieldDefStr.equals(objectFieldDefStr)) {
validationErrorCollector.addError(
error(format("object type '%s' does not implement interface '%s' because field '%s' is defined as '%s' type and not as '%s' type",
objectTyoe.getName(), interfaceType.getName(), interfaceFieldDef.getName(), objectFieldDefStr, interfaceFieldDefStr)));
} else {
checkFieldArgumentEquivalence(objectTyoe, interfaceType, validationErrorCollector, interfaceFieldDef, objectFieldDef);
}
}
private void checkFieldArgumentEquivalence(GraphQLObjectType objectTyoe, GraphQLInterfaceType interfaceType, SchemaValidationErrorCollector validationErrorCollector, GraphQLFieldDefinition interfaceFieldDef, GraphQLFieldDefinition objectFieldDef) {
List<GraphQLArgument> interfaceArgs = interfaceFieldDef.getArguments();
List<GraphQLArgument> objectArgs = objectFieldDef.getArguments();
if (interfaceArgs.size() != objectArgs.size()) {
validationErrorCollector.addError(
error(format("object type '%s' does not implement interface '%s' because field '%s' has a different number of arguments",
objectTyoe.getName(), interfaceType.getName(), interfaceFieldDef.getName())));
} else {
for (int i = 0; i < interfaceArgs.size(); i++) {
GraphQLArgument interfaceArg = interfaceArgs.get(i);
GraphQLArgument objectArg = objectArgs.get(i);
String interfaceArgStr = makeArgStr(interfaceArg);
String objectArgStr = makeArgStr(objectArg);
boolean same = true;
if (!interfaceArgStr.equals(objectArgStr)) {
same = false;
}
if (!Objects.equals(interfaceArg.getDefaultValue(), objectArg.getDefaultValue())) {
same = false;
}
if (!same) {
validationErrorCollector.addError(
error(format("object type '%s' does not implement interface '%s' because field '%s' argument '%s' is defined differently",
objectTyoe.getName(), interfaceType.getName(), interfaceFieldDef.getName(), interfaceArg.getName())));
}
}
}
}
private String makeArgStr(GraphQLArgument argument) {
// we don't do default value checking because toString of getDefaultValue is not guaranteed to be stable
return argument.getName() +
":" +
getUnwrappedTypeName(argument.getType());
}
private SchemaValidationError error(String msg) {
return new SchemaValidationError(ObjectDoesNotImplementItsInterfaces, msg);
}
}