blob: 13da100f7aeb72b111499d578e7d0b19ce323c04 [file] [log] [blame]
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.validateSchema = validateSchema;
exports.assertValidSchema = assertValidSchema;
var _find = _interopRequireDefault(require("../polyfills/find"));
var _flatMap = _interopRequireDefault(require("../polyfills/flatMap"));
var _objectValues5 = _interopRequireDefault(require("../polyfills/objectValues"));
var _inspect = _interopRequireDefault(require("../jsutils/inspect"));
var _GraphQLError = require("../error/GraphQLError");
var _locatedError = require("../error/locatedError");
var _assertValidName = require("../utilities/assertValidName");
var _typeComparators = require("../utilities/typeComparators");
var _directives = require("./directives");
var _introspection = require("./introspection");
var _schema = require("./schema");
var _definition = require("./definition");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Implements the "Type Validation" sub-sections of the specification's
* "Type System" section.
*
* Validation runs synchronously, returning an array of encountered errors, or
* an empty array if no errors were encountered and the Schema is valid.
*/
function validateSchema(schema) {
// First check to ensure the provided value is in fact a GraphQLSchema.
(0, _schema.assertSchema)(schema); // If this Schema has already been validated, return the previous results.
if (schema.__validationErrors) {
return schema.__validationErrors;
} // Validate the schema, producing a list of errors.
var context = new SchemaValidationContext(schema);
validateRootTypes(context);
validateDirectives(context);
validateTypes(context); // Persist the results of validation before returning to ensure validation
// does not run multiple times for this schema.
var errors = context.getErrors();
schema.__validationErrors = errors;
return errors;
}
/**
* Utility function which asserts a schema is valid by throwing an error if
* it is invalid.
*/
function assertValidSchema(schema) {
var errors = validateSchema(schema);
if (errors.length !== 0) {
throw new Error(errors.map(function (error) {
return error.message;
}).join('\n\n'));
}
}
var SchemaValidationContext =
/*#__PURE__*/
function () {
function SchemaValidationContext(schema) {
this._errors = [];
this.schema = schema;
}
var _proto = SchemaValidationContext.prototype;
_proto.reportError = function reportError(message, nodes) {
var _nodes = Array.isArray(nodes) ? nodes.filter(Boolean) : nodes;
this.addError(new _GraphQLError.GraphQLError(message, _nodes));
};
_proto.addError = function addError(error) {
this._errors.push(error);
};
_proto.getErrors = function getErrors() {
return this._errors;
};
return SchemaValidationContext;
}();
function validateRootTypes(context) {
var schema = context.schema;
var queryType = schema.getQueryType();
if (!queryType) {
context.reportError('Query root type must be provided.', schema.astNode);
} else if (!(0, _definition.isObjectType)(queryType)) {
context.reportError("Query root type must be Object type, it cannot be ".concat((0, _inspect.default)(queryType), "."), getOperationTypeNode(schema, queryType, 'query'));
}
var mutationType = schema.getMutationType();
if (mutationType && !(0, _definition.isObjectType)(mutationType)) {
context.reportError('Mutation root type must be Object type if provided, it cannot be ' + "".concat((0, _inspect.default)(mutationType), "."), getOperationTypeNode(schema, mutationType, 'mutation'));
}
var subscriptionType = schema.getSubscriptionType();
if (subscriptionType && !(0, _definition.isObjectType)(subscriptionType)) {
context.reportError('Subscription root type must be Object type if provided, it cannot be ' + "".concat((0, _inspect.default)(subscriptionType), "."), getOperationTypeNode(schema, subscriptionType, 'subscription'));
}
}
function getOperationTypeNode(schema, type, operation) {
var operationNodes = getAllSubNodes(schema, function (node) {
return node.operationTypes;
});
for (var _i2 = 0; _i2 < operationNodes.length; _i2++) {
var node = operationNodes[_i2];
if (node.operation === operation) {
return node.type;
}
}
return type.astNode;
}
function validateDirectives(context) {
for (var _i4 = 0, _context$schema$getDi2 = context.schema.getDirectives(); _i4 < _context$schema$getDi2.length; _i4++) {
var directive = _context$schema$getDi2[_i4];
// Ensure all directives are in fact GraphQL directives.
if (!(0, _directives.isDirective)(directive)) {
context.reportError("Expected directive but got: ".concat((0, _inspect.default)(directive), "."), directive === null || directive === void 0 ? void 0 : directive.astNode);
continue;
} // Ensure they are named correctly.
validateName(context, directive); // TODO: Ensure proper locations.
// Ensure the arguments are valid.
for (var _i6 = 0, _directive$args2 = directive.args; _i6 < _directive$args2.length; _i6++) {
var arg = _directive$args2[_i6];
// Ensure they are named correctly.
validateName(context, arg); // Ensure the type is an input type.
if (!(0, _definition.isInputType)(arg.type)) {
context.reportError("The type of @".concat(directive.name, "(").concat(arg.name, ":) must be Input Type ") + "but got: ".concat((0, _inspect.default)(arg.type), "."), arg.astNode);
}
}
}
}
function validateName(context, node) {
// Ensure names are valid, however introspection types opt out.
var error = (0, _assertValidName.isValidNameError)(node.name);
if (error) {
context.addError((0, _locatedError.locatedError)(error, node.astNode));
}
}
function validateTypes(context) {
var validateInputObjectCircularRefs = createInputObjectCircularRefsValidator(context);
var typeMap = context.schema.getTypeMap();
for (var _i8 = 0, _objectValues2 = (0, _objectValues5.default)(typeMap); _i8 < _objectValues2.length; _i8++) {
var type = _objectValues2[_i8];
// Ensure all provided types are in fact GraphQL type.
if (!(0, _definition.isNamedType)(type)) {
context.reportError("Expected GraphQL named type but got: ".concat((0, _inspect.default)(type), "."), type.astNode);
continue;
} // Ensure it is named correctly (excluding introspection types).
if (!(0, _introspection.isIntrospectionType)(type)) {
validateName(context, type);
}
if ((0, _definition.isObjectType)(type)) {
// Ensure fields are valid
validateFields(context, type); // Ensure objects implement the interfaces they claim to.
validateInterfaces(context, type);
} else if ((0, _definition.isInterfaceType)(type)) {
// Ensure fields are valid.
validateFields(context, type); // Ensure interfaces implement the interfaces they claim to.
validateInterfaces(context, type);
} else if ((0, _definition.isUnionType)(type)) {
// Ensure Unions include valid member types.
validateUnionMembers(context, type);
} else if ((0, _definition.isEnumType)(type)) {
// Ensure Enums have valid values.
validateEnumValues(context, type);
} else if ((0, _definition.isInputObjectType)(type)) {
// Ensure Input Object fields are valid.
validateInputFields(context, type); // Ensure Input Objects do not contain non-nullable circular references
validateInputObjectCircularRefs(type);
}
}
}
function validateFields(context, type) {
var fields = (0, _objectValues5.default)(type.getFields()); // Objects and Interfaces both must define one or more fields.
if (fields.length === 0) {
context.reportError("Type ".concat(type.name, " must define one or more fields."), getAllNodes(type));
}
for (var _i10 = 0; _i10 < fields.length; _i10++) {
var field = fields[_i10];
// Ensure they are named correctly.
validateName(context, field); // Ensure the type is an output type
if (!(0, _definition.isOutputType)(field.type)) {
var _field$astNode;
context.reportError("The type of ".concat(type.name, ".").concat(field.name, " must be Output Type ") + "but got: ".concat((0, _inspect.default)(field.type), "."), (_field$astNode = field.astNode) === null || _field$astNode === void 0 ? void 0 : _field$astNode.type);
} // Ensure the arguments are valid
for (var _i12 = 0, _field$args2 = field.args; _i12 < _field$args2.length; _i12++) {
var arg = _field$args2[_i12];
var argName = arg.name; // Ensure they are named correctly.
validateName(context, arg); // Ensure the type is an input type
if (!(0, _definition.isInputType)(arg.type)) {
var _arg$astNode;
context.reportError("The type of ".concat(type.name, ".").concat(field.name, "(").concat(argName, ":) must be Input ") + "Type but got: ".concat((0, _inspect.default)(arg.type), "."), (_arg$astNode = arg.astNode) === null || _arg$astNode === void 0 ? void 0 : _arg$astNode.type);
}
}
}
}
function validateInterfaces(context, type) {
var ifaceTypeNames = Object.create(null);
for (var _i14 = 0, _type$getInterfaces2 = type.getInterfaces(); _i14 < _type$getInterfaces2.length; _i14++) {
var iface = _type$getInterfaces2[_i14];
if (!(0, _definition.isInterfaceType)(iface)) {
context.reportError("Type ".concat((0, _inspect.default)(type), " must only implement Interface types, ") + "it cannot implement ".concat((0, _inspect.default)(iface), "."), getAllImplementsInterfaceNodes(type, iface));
continue;
}
if (type === iface) {
context.reportError("Type ".concat(type.name, " cannot implement itself because it would create a circular reference."), getAllImplementsInterfaceNodes(type, iface));
continue;
}
if (ifaceTypeNames[iface.name]) {
context.reportError("Type ".concat(type.name, " can only implement ").concat(iface.name, " once."), getAllImplementsInterfaceNodes(type, iface));
continue;
}
ifaceTypeNames[iface.name] = true;
validateTypeImplementsAncestors(context, type, iface);
validateTypeImplementsInterface(context, type, iface);
}
}
function validateTypeImplementsInterface(context, type, iface) {
var typeFieldMap = type.getFields(); // Assert each interface field is implemented.
for (var _i16 = 0, _objectValues4 = (0, _objectValues5.default)(iface.getFields()); _i16 < _objectValues4.length; _i16++) {
var ifaceField = _objectValues4[_i16];
var fieldName = ifaceField.name;
var typeField = typeFieldMap[fieldName]; // Assert interface field exists on type.
if (!typeField) {
context.reportError("Interface field ".concat(iface.name, ".").concat(fieldName, " expected but ").concat(type.name, " does not provide it."), [ifaceField.astNode].concat(getAllNodes(type)));
continue;
} // Assert interface field type is satisfied by type field type, by being
// a valid subtype. (covariant)
if (!(0, _typeComparators.isTypeSubTypeOf)(context.schema, typeField.type, ifaceField.type)) {
context.reportError("Interface field ".concat(iface.name, ".").concat(fieldName, " expects type ") + "".concat((0, _inspect.default)(ifaceField.type), " but ").concat(type.name, ".").concat(fieldName, " ") + "is type ".concat((0, _inspect.default)(typeField.type), "."), [ifaceField.astNode.type, typeField.astNode.type]);
} // Assert each interface field arg is implemented.
var _loop = function _loop(_i18, _ifaceField$args2) {
var ifaceArg = _ifaceField$args2[_i18];
var argName = ifaceArg.name;
var typeArg = (0, _find.default)(typeField.args, function (arg) {
return arg.name === argName;
}); // Assert interface field arg exists on object field.
if (!typeArg) {
context.reportError("Interface field argument ".concat(iface.name, ".").concat(fieldName, "(").concat(argName, ":) expected but ").concat(type.name, ".").concat(fieldName, " does not provide it."), [ifaceArg.astNode, typeField.astNode]);
return "continue";
} // Assert interface field arg type matches object field arg type.
// (invariant)
// TODO: change to contravariant?
if (!(0, _typeComparators.isEqualType)(ifaceArg.type, typeArg.type)) {
context.reportError("Interface field argument ".concat(iface.name, ".").concat(fieldName, "(").concat(argName, ":) ") + "expects type ".concat((0, _inspect.default)(ifaceArg.type), " but ") + "".concat(type.name, ".").concat(fieldName, "(").concat(argName, ":) is type ") + "".concat((0, _inspect.default)(typeArg.type), "."), [ifaceArg.astNode.type, typeArg.astNode.type]);
} // TODO: validate default values?
};
for (var _i18 = 0, _ifaceField$args2 = ifaceField.args; _i18 < _ifaceField$args2.length; _i18++) {
var _ret = _loop(_i18, _ifaceField$args2);
if (_ret === "continue") continue;
} // Assert additional arguments must not be required.
var _loop2 = function _loop2(_i20, _typeField$args2) {
var typeArg = _typeField$args2[_i20];
var argName = typeArg.name;
var ifaceArg = (0, _find.default)(ifaceField.args, function (arg) {
return arg.name === argName;
});
if (!ifaceArg && (0, _definition.isRequiredArgument)(typeArg)) {
context.reportError("Object field ".concat(type.name, ".").concat(fieldName, " includes required argument ").concat(argName, " that is missing from the Interface field ").concat(iface.name, ".").concat(fieldName, "."), [typeArg.astNode, ifaceField.astNode]);
}
};
for (var _i20 = 0, _typeField$args2 = typeField.args; _i20 < _typeField$args2.length; _i20++) {
_loop2(_i20, _typeField$args2);
}
}
}
function validateTypeImplementsAncestors(context, type, iface) {
var ifaceInterfaces = type.getInterfaces();
for (var _i22 = 0, _iface$getInterfaces2 = iface.getInterfaces(); _i22 < _iface$getInterfaces2.length; _i22++) {
var transitive = _iface$getInterfaces2[_i22];
if (ifaceInterfaces.indexOf(transitive) === -1) {
context.reportError(transitive === type ? "Type ".concat(type.name, " cannot implement ").concat(iface.name, " because it would create a circular reference.") : "Type ".concat(type.name, " must implement ").concat(transitive.name, " because it is implemented by ").concat(iface.name, "."), [].concat(getAllImplementsInterfaceNodes(iface, transitive), getAllImplementsInterfaceNodes(type, iface)));
}
}
}
function validateUnionMembers(context, union) {
var memberTypes = union.getTypes();
if (memberTypes.length === 0) {
context.reportError("Union type ".concat(union.name, " must define one or more member types."), getAllNodes(union));
}
var includedTypeNames = Object.create(null);
for (var _i24 = 0; _i24 < memberTypes.length; _i24++) {
var memberType = memberTypes[_i24];
if (includedTypeNames[memberType.name]) {
context.reportError("Union type ".concat(union.name, " can only include type ").concat(memberType.name, " once."), getUnionMemberTypeNodes(union, memberType.name));
continue;
}
includedTypeNames[memberType.name] = true;
if (!(0, _definition.isObjectType)(memberType)) {
context.reportError("Union type ".concat(union.name, " can only include Object types, ") + "it cannot include ".concat((0, _inspect.default)(memberType), "."), getUnionMemberTypeNodes(union, String(memberType)));
}
}
}
function validateEnumValues(context, enumType) {
var enumValues = enumType.getValues();
if (enumValues.length === 0) {
context.reportError("Enum type ".concat(enumType.name, " must define one or more values."), getAllNodes(enumType));
}
for (var _i26 = 0; _i26 < enumValues.length; _i26++) {
var enumValue = enumValues[_i26];
var valueName = enumValue.name; // Ensure valid name.
validateName(context, enumValue);
if (valueName === 'true' || valueName === 'false' || valueName === 'null') {
context.reportError("Enum type ".concat(enumType.name, " cannot include value: ").concat(valueName, "."), enumValue.astNode);
}
}
}
function validateInputFields(context, inputObj) {
var fields = (0, _objectValues5.default)(inputObj.getFields());
if (fields.length === 0) {
context.reportError("Input Object type ".concat(inputObj.name, " must define one or more fields."), getAllNodes(inputObj));
} // Ensure the arguments are valid
for (var _i28 = 0; _i28 < fields.length; _i28++) {
var field = fields[_i28];
// Ensure they are named correctly.
validateName(context, field); // Ensure the type is an input type
if (!(0, _definition.isInputType)(field.type)) {
var _field$astNode2;
context.reportError("The type of ".concat(inputObj.name, ".").concat(field.name, " must be Input Type ") + "but got: ".concat((0, _inspect.default)(field.type), "."), (_field$astNode2 = field.astNode) === null || _field$astNode2 === void 0 ? void 0 : _field$astNode2.type);
}
}
}
function createInputObjectCircularRefsValidator(context) {
// Modified copy of algorithm from 'src/validation/rules/NoFragmentCycles.js'.
// Tracks already visited types to maintain O(N) and to ensure that cycles
// are not redundantly reported.
var visitedTypes = Object.create(null); // Array of types nodes used to produce meaningful errors
var fieldPath = []; // Position in the type path
var fieldPathIndexByTypeName = Object.create(null);
return detectCycleRecursive; // This does a straight-forward DFS to find cycles.
// It does not terminate when a cycle was found but continues to explore
// the graph to find all possible cycles.
function detectCycleRecursive(inputObj) {
if (visitedTypes[inputObj.name]) {
return;
}
visitedTypes[inputObj.name] = true;
fieldPathIndexByTypeName[inputObj.name] = fieldPath.length;
var fields = (0, _objectValues5.default)(inputObj.getFields());
for (var _i30 = 0; _i30 < fields.length; _i30++) {
var field = fields[_i30];
if ((0, _definition.isNonNullType)(field.type) && (0, _definition.isInputObjectType)(field.type.ofType)) {
var fieldType = field.type.ofType;
var cycleIndex = fieldPathIndexByTypeName[fieldType.name];
fieldPath.push(field);
if (cycleIndex === undefined) {
detectCycleRecursive(fieldType);
} else {
var cyclePath = fieldPath.slice(cycleIndex);
var pathStr = cyclePath.map(function (fieldObj) {
return fieldObj.name;
}).join('.');
context.reportError("Cannot reference Input Object \"".concat(fieldType.name, "\" within itself through a series of non-null fields: \"").concat(pathStr, "\"."), cyclePath.map(function (fieldObj) {
return fieldObj.astNode;
}));
}
fieldPath.pop();
}
}
fieldPathIndexByTypeName[inputObj.name] = undefined;
}
}
function getAllNodes(object) {
var astNode = object.astNode,
extensionASTNodes = object.extensionASTNodes;
return astNode ? extensionASTNodes ? [astNode].concat(extensionASTNodes) : [astNode] : extensionASTNodes !== null && extensionASTNodes !== void 0 ? extensionASTNodes : [];
}
function getAllSubNodes(object, getter) {
/* istanbul ignore next (See https://github.com/graphql/graphql-js/issues/2203) */
return (0, _flatMap.default)(getAllNodes(object), function (item) {
var _getter;
return (_getter = getter(item)) !== null && _getter !== void 0 ? _getter : [];
});
}
function getAllImplementsInterfaceNodes(type, iface) {
return getAllSubNodes(type, function (typeNode) {
return typeNode.interfaces;
}).filter(function (ifaceNode) {
return ifaceNode.name.value === iface.name;
});
}
function getUnionMemberTypeNodes(union, typeName) {
return getAllSubNodes(union, function (unionNode) {
return unionNode.types;
}).filter(function (typeNode) {
return typeNode.name.value === typeName;
});
}