UNPKG

graphql

Version:

A Query Language and Runtime which can target any service.

761 lines (640 loc) 27 kB
import find from '../polyfills/find'; import flatMap from '../polyfills/flatMap'; import objectValues from '../polyfills/objectValues'; import objectEntries from '../polyfills/objectEntries'; import { isObjectType, isInterfaceType, isUnionType, isEnumType, isInputObjectType, isNamedType, isNonNullType, isInputType, isOutputType, isRequiredArgument } from './definition'; import { isDirective } from './directives'; import { isIntrospectionType } from './introspection'; import { assertSchema } from './schema'; import inspect from '../jsutils/inspect'; import { GraphQLError } from '../error/GraphQLError'; import { isValidNameError } from '../utilities/assertValidName'; import { isEqualType, isTypeSubTypeOf } from '../utilities/typeComparators'; /** * 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. */ export function validateSchema(schema) { // First check to ensure the provided value is in fact a GraphQLSchema. 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. */ export 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(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 (!isObjectType(queryType)) { context.reportError("Query root type must be Object type, it cannot be ".concat(inspect(queryType), "."), getOperationTypeNode(schema, queryType, 'query')); } var mutationType = schema.getMutationType(); if (mutationType && !isObjectType(mutationType)) { context.reportError('Mutation root type must be Object type if provided, it cannot be ' + "".concat(inspect(mutationType), "."), getOperationTypeNode(schema, mutationType, 'mutation')); } var subscriptionType = schema.getSubscriptionType(); if (subscriptionType && !isObjectType(subscriptionType)) { context.reportError('Subscription root type must be Object type if provided, it cannot be ' + "".concat(inspect(subscriptionType), "."), getOperationTypeNode(schema, subscriptionType, 'subscription')); } } function getOperationTypeNode(schema, type, operation) { var operationNodes = getAllSubNodes(schema, function (node) { return node.operationTypes; }); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = operationNodes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var node = _step.value; if (node.operation === operation) { return node.type; } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return type.astNode; } function validateDirectives(context) { var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = context.schema.getDirectives()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var directive = _step2.value; // Ensure all directives are in fact GraphQL directives. if (!isDirective(directive)) { context.reportError("Expected directive but got: ".concat(inspect(directive), "."), directive && directive.astNode); continue; } // Ensure they are named correctly. validateName(context, directive); // TODO: Ensure proper locations. // Ensure the arguments are valid. var argNames = Object.create(null); var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { var _loop = function _loop() { var arg = _step3.value; var argName = arg.name; // Ensure they are named correctly. validateName(context, arg); // Ensure they are unique per directive. if (argNames[argName]) { context.reportError("Argument @".concat(directive.name, "(").concat(argName, ":) can only be defined once."), directive.astNode && directive.args.filter(function (_ref) { var name = _ref.name; return name === argName; }).map(function (_ref2) { var astNode = _ref2.astNode; return astNode; })); return "continue"; } argNames[argName] = true; // Ensure the type is an input type. if (!isInputType(arg.type)) { context.reportError("The type of @".concat(directive.name, "(").concat(argName, ":) must be Input Type ") + "but got: ".concat(inspect(arg.type), "."), arg.astNode); } }; for (var _iterator3 = directive.args[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var _ret = _loop(); if (_ret === "continue") continue; } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return != null) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return != null) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } } function validateName(context, node) { // If a schema explicitly allows some legacy name which is no longer valid, // allow it to be assumed valid. if (context.schema.__allowedLegacyNames.indexOf(node.name) !== -1) { return; } // Ensure names are valid, however introspection types opt out. var error = isValidNameError(node.name, node.astNode || undefined); if (error) { context.addError(error); } } function validateTypes(context) { var validateInputObjectCircularRefs = createInputObjectCircularRefsValidator(context); var typeMap = context.schema.getTypeMap(); var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = objectValues(typeMap)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var type = _step4.value; // Ensure all provided types are in fact GraphQL type. if (!isNamedType(type)) { context.reportError("Expected GraphQL named type but got: ".concat(inspect(type), "."), type && type.astNode); continue; } // Ensure it is named correctly (excluding introspection types). if (!isIntrospectionType(type)) { validateName(context, type); } if (isObjectType(type)) { // Ensure fields are valid validateFields(context, type); // Ensure objects implement the interfaces they claim to. validateObjectInterfaces(context, type); } else if (isInterfaceType(type)) { // Ensure fields are valid. validateFields(context, type); } else if (isUnionType(type)) { // Ensure Unions include valid member types. validateUnionMembers(context, type); } else if (isEnumType(type)) { // Ensure Enums have valid values. validateEnumValues(context, type); } else if (isInputObjectType(type)) { // Ensure Input Object fields are valid. validateInputFields(context, type); // Ensure Input Objects do not contain non-nullable circular references validateInputObjectCircularRefs(type); } } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return != null) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } } function validateFields(context, type) { var fields = objectValues(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)); } var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = fields[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { var field = _step5.value; // Ensure they are named correctly. validateName(context, field); // Ensure the type is an output type if (!isOutputType(field.type)) { context.reportError("The type of ".concat(type.name, ".").concat(field.name, " must be Output Type ") + "but got: ".concat(inspect(field.type), "."), field.astNode && field.astNode.type); } // Ensure the arguments are valid var argNames = Object.create(null); var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { var _loop2 = function _loop2() { var arg = _step6.value; var argName = arg.name; // Ensure they are named correctly. validateName(context, arg); // Ensure they are unique per field. if (argNames[argName]) { context.reportError("Field argument ".concat(type.name, ".").concat(field.name, "(").concat(argName, ":) can only be defined once."), field.args.filter(function (_ref3) { var name = _ref3.name; return name === argName; }).map(function (_ref4) { var astNode = _ref4.astNode; return astNode; })); } argNames[argName] = true; // Ensure the type is an input type if (!isInputType(arg.type)) { context.reportError("The type of ".concat(type.name, ".").concat(field.name, "(").concat(argName, ":) must be Input ") + "Type but got: ".concat(inspect(arg.type), "."), arg.astNode && arg.astNode.type); } }; for (var _iterator6 = field.args[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { _loop2(); } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6.return != null) { _iterator6.return(); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5.return != null) { _iterator5.return(); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } } function validateObjectInterfaces(context, object) { var implementedTypeNames = Object.create(null); var _iteratorNormalCompletion7 = true; var _didIteratorError7 = false; var _iteratorError7 = undefined; try { for (var _iterator7 = object.getInterfaces()[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { var iface = _step7.value; if (!isInterfaceType(iface)) { context.reportError("Type ".concat(inspect(object), " must only implement Interface types, ") + "it cannot implement ".concat(inspect(iface), "."), getAllImplementsInterfaceNodes(object, iface)); continue; } if (implementedTypeNames[iface.name]) { context.reportError("Type ".concat(object.name, " can only implement ").concat(iface.name, " once."), getAllImplementsInterfaceNodes(object, iface)); continue; } implementedTypeNames[iface.name] = true; validateObjectImplementsInterface(context, object, iface); } } catch (err) { _didIteratorError7 = true; _iteratorError7 = err; } finally { try { if (!_iteratorNormalCompletion7 && _iterator7.return != null) { _iterator7.return(); } } finally { if (_didIteratorError7) { throw _iteratorError7; } } } } function validateObjectImplementsInterface(context, object, iface) { var objectFieldMap = object.getFields(); var ifaceFieldMap = iface.getFields(); // Assert each interface field is implemented. var _iteratorNormalCompletion8 = true; var _didIteratorError8 = false; var _iteratorError8 = undefined; try { for (var _iterator8 = objectEntries(ifaceFieldMap)[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { var _ref6 = _step8.value; var fieldName = _ref6[0]; var ifaceField = _ref6[1]; var objectField = objectFieldMap[fieldName]; // Assert interface field exists on object. if (!objectField) { context.reportError("Interface field ".concat(iface.name, ".").concat(fieldName, " expected but ").concat(object.name, " does not provide it."), [ifaceField.astNode].concat(getAllNodes(object))); continue; } // Assert interface field type is satisfied by object field type, by being // a valid subtype. (covariant) if (!isTypeSubTypeOf(context.schema, objectField.type, ifaceField.type)) { context.reportError("Interface field ".concat(iface.name, ".").concat(fieldName, " expects type ") + "".concat(inspect(ifaceField.type), " but ").concat(object.name, ".").concat(fieldName, " ") + "is type ".concat(inspect(objectField.type), "."), [ifaceField.astNode && ifaceField.astNode.type, objectField.astNode && objectField.astNode.type]); } // Assert each interface field arg is implemented. var _iteratorNormalCompletion9 = true; var _didIteratorError9 = false; var _iteratorError9 = undefined; try { var _loop3 = function _loop3() { var ifaceArg = _step9.value; var argName = ifaceArg.name; var objectArg = find(objectField.args, function (arg) { return arg.name === argName; }); // Assert interface field arg exists on object field. if (!objectArg) { context.reportError("Interface field argument ".concat(iface.name, ".").concat(fieldName, "(").concat(argName, ":) expected but ").concat(object.name, ".").concat(fieldName, " does not provide it."), [ifaceArg.astNode, objectField.astNode]); return "continue"; } // Assert interface field arg type matches object field arg type. // (invariant) // TODO: change to contravariant? if (!isEqualType(ifaceArg.type, objectArg.type)) { context.reportError("Interface field argument ".concat(iface.name, ".").concat(fieldName, "(").concat(argName, ":) ") + "expects type ".concat(inspect(ifaceArg.type), " but ") + "".concat(object.name, ".").concat(fieldName, "(").concat(argName, ":) is type ") + "".concat(inspect(objectArg.type), "."), [ifaceArg.astNode && ifaceArg.astNode.type, objectArg.astNode && objectArg.astNode.type]); } // TODO: validate default values? }; for (var _iterator9 = ifaceField.args[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) { var _ret2 = _loop3(); if (_ret2 === "continue") continue; } // Assert additional arguments must not be required. } catch (err) { _didIteratorError9 = true; _iteratorError9 = err; } finally { try { if (!_iteratorNormalCompletion9 && _iterator9.return != null) { _iterator9.return(); } } finally { if (_didIteratorError9) { throw _iteratorError9; } } } var _iteratorNormalCompletion10 = true; var _didIteratorError10 = false; var _iteratorError10 = undefined; try { var _loop4 = function _loop4() { var objectArg = _step10.value; var argName = objectArg.name; var ifaceArg = find(ifaceField.args, function (arg) { return arg.name === argName; }); if (!ifaceArg && isRequiredArgument(objectArg)) { context.reportError("Object field ".concat(object.name, ".").concat(fieldName, " includes required argument ").concat(argName, " that is missing from the Interface field ").concat(iface.name, ".").concat(fieldName, "."), [objectArg.astNode, ifaceField.astNode]); } }; for (var _iterator10 = objectField.args[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) { _loop4(); } } catch (err) { _didIteratorError10 = true; _iteratorError10 = err; } finally { try { if (!_iteratorNormalCompletion10 && _iterator10.return != null) { _iterator10.return(); } } finally { if (_didIteratorError10) { throw _iteratorError10; } } } } } catch (err) { _didIteratorError8 = true; _iteratorError8 = err; } finally { try { if (!_iteratorNormalCompletion8 && _iterator8.return != null) { _iterator8.return(); } } finally { if (_didIteratorError8) { throw _iteratorError8; } } } } 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); var _iteratorNormalCompletion11 = true; var _didIteratorError11 = false; var _iteratorError11 = undefined; try { for (var _iterator11 = memberTypes[Symbol.iterator](), _step11; !(_iteratorNormalCompletion11 = (_step11 = _iterator11.next()).done); _iteratorNormalCompletion11 = true) { var memberType = _step11.value; 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 (!isObjectType(memberType)) { context.reportError("Union type ".concat(union.name, " can only include Object types, ") + "it cannot include ".concat(inspect(memberType), "."), getUnionMemberTypeNodes(union, String(memberType))); } } } catch (err) { _didIteratorError11 = true; _iteratorError11 = err; } finally { try { if (!_iteratorNormalCompletion11 && _iterator11.return != null) { _iterator11.return(); } } finally { if (_didIteratorError11) { throw _iteratorError11; } } } } 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)); } var _iteratorNormalCompletion12 = true; var _didIteratorError12 = false; var _iteratorError12 = undefined; try { for (var _iterator12 = enumValues[Symbol.iterator](), _step12; !(_iteratorNormalCompletion12 = (_step12 = _iterator12.next()).done); _iteratorNormalCompletion12 = true) { var enumValue = _step12.value; 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); } } } catch (err) { _didIteratorError12 = true; _iteratorError12 = err; } finally { try { if (!_iteratorNormalCompletion12 && _iterator12.return != null) { _iterator12.return(); } } finally { if (_didIteratorError12) { throw _iteratorError12; } } } } function validateInputFields(context, inputObj) { var fields = objectValues(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 var _iteratorNormalCompletion13 = true; var _didIteratorError13 = false; var _iteratorError13 = undefined; try { for (var _iterator13 = fields[Symbol.iterator](), _step13; !(_iteratorNormalCompletion13 = (_step13 = _iterator13.next()).done); _iteratorNormalCompletion13 = true) { var field = _step13.value; // Ensure they are named correctly. validateName(context, field); // Ensure the type is an input type if (!isInputType(field.type)) { context.reportError("The type of ".concat(inputObj.name, ".").concat(field.name, " must be Input Type ") + "but got: ".concat(inspect(field.type), "."), field.astNode && field.astNode.type); } } } catch (err) { _didIteratorError13 = true; _iteratorError13 = err; } finally { try { if (!_iteratorNormalCompletion13 && _iterator13.return != null) { _iterator13.return(); } } finally { if (_didIteratorError13) { throw _iteratorError13; } } } } 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 = objectValues(inputObj.getFields()); var _iteratorNormalCompletion14 = true; var _didIteratorError14 = false; var _iteratorError14 = undefined; try { for (var _iterator14 = fields[Symbol.iterator](), _step14; !(_iteratorNormalCompletion14 = (_step14 = _iterator14.next()).done); _iteratorNormalCompletion14 = true) { var field = _step14.value; if (isNonNullType(field.type) && 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(); } } } catch (err) { _didIteratorError14 = true; _iteratorError14 = err; } finally { try { if (!_iteratorNormalCompletion14 && _iterator14.return != null) { _iterator14.return(); } } finally { if (_didIteratorError14) { throw _iteratorError14; } } } fieldPathIndexByTypeName[inputObj.name] = undefined; } } function getAllNodes(object) { var astNode = object.astNode, extensionASTNodes = object.extensionASTNodes; return astNode ? extensionASTNodes ? [astNode].concat(extensionASTNodes) : [astNode] : extensionASTNodes || []; } function getAllSubNodes(object, getter) { return flatMap(getAllNodes(object), function (item) { return getter(item) || []; }); } 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; }); }