graphql
Version:
A Query Language and Runtime which can target any service.
761 lines (640 loc) • 27 kB
JavaScript
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;
});
}