UNPKG

23.2 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.validateSchema = validateSchema;
7exports.assertValidSchema = assertValidSchema;
8
9var _find = _interopRequireDefault(require("../polyfills/find.js"));
10
11var _objectValues5 = _interopRequireDefault(require("../polyfills/objectValues.js"));
12
13var _inspect = _interopRequireDefault(require("../jsutils/inspect.js"));
14
15var _GraphQLError = require("../error/GraphQLError.js");
16
17var _locatedError = require("../error/locatedError.js");
18
19var _assertValidName = require("../utilities/assertValidName.js");
20
21var _typeComparators = require("../utilities/typeComparators.js");
22
23var _schema = require("./schema.js");
24
25var _introspection = require("./introspection.js");
26
27var _directives = require("./directives.js");
28
29var _definition = require("./definition.js");
30
31function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
32
33/**
34 * Implements the "Type Validation" sub-sections of the specification's
35 * "Type System" section.
36 *
37 * Validation runs synchronously, returning an array of encountered errors, or
38 * an empty array if no errors were encountered and the Schema is valid.
39 */
40function validateSchema(schema) {
41 // First check to ensure the provided value is in fact a GraphQLSchema.
42 (0, _schema.assertSchema)(schema); // If this Schema has already been validated, return the previous results.
43
44 if (schema.__validationErrors) {
45 return schema.__validationErrors;
46 } // Validate the schema, producing a list of errors.
47
48
49 var context = new SchemaValidationContext(schema);
50 validateRootTypes(context);
51 validateDirectives(context);
52 validateTypes(context); // Persist the results of validation before returning to ensure validation
53 // does not run multiple times for this schema.
54
55 var errors = context.getErrors();
56 schema.__validationErrors = errors;
57 return errors;
58}
59/**
60 * Utility function which asserts a schema is valid by throwing an error if
61 * it is invalid.
62 */
63
64
65function assertValidSchema(schema) {
66 var errors = validateSchema(schema);
67
68 if (errors.length !== 0) {
69 throw new Error(errors.map(function (error) {
70 return error.message;
71 }).join('\n\n'));
72 }
73}
74
75var SchemaValidationContext = /*#__PURE__*/function () {
76 function SchemaValidationContext(schema) {
77 this._errors = [];
78 this.schema = schema;
79 }
80
81 var _proto = SchemaValidationContext.prototype;
82
83 _proto.reportError = function reportError(message, nodes) {
84 var _nodes = Array.isArray(nodes) ? nodes.filter(Boolean) : nodes;
85
86 this.addError(new _GraphQLError.GraphQLError(message, _nodes));
87 };
88
89 _proto.addError = function addError(error) {
90 this._errors.push(error);
91 };
92
93 _proto.getErrors = function getErrors() {
94 return this._errors;
95 };
96
97 return SchemaValidationContext;
98}();
99
100function validateRootTypes(context) {
101 var schema = context.schema;
102 var queryType = schema.getQueryType();
103
104 if (!queryType) {
105 context.reportError('Query root type must be provided.', schema.astNode);
106 } else if (!(0, _definition.isObjectType)(queryType)) {
107 var _getOperationTypeNode;
108
109 context.reportError("Query root type must be Object type, it cannot be ".concat((0, _inspect.default)(queryType), "."), (_getOperationTypeNode = getOperationTypeNode(schema, 'query')) !== null && _getOperationTypeNode !== void 0 ? _getOperationTypeNode : queryType.astNode);
110 }
111
112 var mutationType = schema.getMutationType();
113
114 if (mutationType && !(0, _definition.isObjectType)(mutationType)) {
115 var _getOperationTypeNode2;
116
117 context.reportError('Mutation root type must be Object type if provided, it cannot be ' + "".concat((0, _inspect.default)(mutationType), "."), (_getOperationTypeNode2 = getOperationTypeNode(schema, 'mutation')) !== null && _getOperationTypeNode2 !== void 0 ? _getOperationTypeNode2 : mutationType.astNode);
118 }
119
120 var subscriptionType = schema.getSubscriptionType();
121
122 if (subscriptionType && !(0, _definition.isObjectType)(subscriptionType)) {
123 var _getOperationTypeNode3;
124
125 context.reportError('Subscription root type must be Object type if provided, it cannot be ' + "".concat((0, _inspect.default)(subscriptionType), "."), (_getOperationTypeNode3 = getOperationTypeNode(schema, 'subscription')) !== null && _getOperationTypeNode3 !== void 0 ? _getOperationTypeNode3 : subscriptionType.astNode);
126 }
127}
128
129function getOperationTypeNode(schema, operation) {
130 var operationNodes = getAllSubNodes(schema, function (node) {
131 return node.operationTypes;
132 });
133
134 for (var _i2 = 0; _i2 < operationNodes.length; _i2++) {
135 var node = operationNodes[_i2];
136
137 if (node.operation === operation) {
138 return node.type;
139 }
140 }
141
142 return undefined;
143}
144
145function validateDirectives(context) {
146 for (var _i4 = 0, _context$schema$getDi2 = context.schema.getDirectives(); _i4 < _context$schema$getDi2.length; _i4++) {
147 var directive = _context$schema$getDi2[_i4];
148
149 // Ensure all directives are in fact GraphQL directives.
150 if (!(0, _directives.isDirective)(directive)) {
151 context.reportError("Expected directive but got: ".concat((0, _inspect.default)(directive), "."), directive === null || directive === void 0 ? void 0 : directive.astNode);
152 continue;
153 } // Ensure they are named correctly.
154
155
156 validateName(context, directive); // TODO: Ensure proper locations.
157 // Ensure the arguments are valid.
158
159 for (var _i6 = 0, _directive$args2 = directive.args; _i6 < _directive$args2.length; _i6++) {
160 var arg = _directive$args2[_i6];
161 // Ensure they are named correctly.
162 validateName(context, arg); // Ensure the type is an input type.
163
164 if (!(0, _definition.isInputType)(arg.type)) {
165 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);
166 }
167
168 if ((0, _definition.isRequiredArgument)(arg) && arg.deprecationReason != null) {
169 var _arg$astNode;
170
171 context.reportError("Required argument @".concat(directive.name, "(").concat(arg.name, ":) cannot be deprecated."), [getDeprecatedDirectiveNode(arg.astNode), // istanbul ignore next (TODO need to write coverage tests)
172 (_arg$astNode = arg.astNode) === null || _arg$astNode === void 0 ? void 0 : _arg$astNode.type]);
173 }
174 }
175 }
176}
177
178function validateName(context, node) {
179 // Ensure names are valid, however introspection types opt out.
180 var error = (0, _assertValidName.isValidNameError)(node.name);
181
182 if (error) {
183 context.addError((0, _locatedError.locatedError)(error, node.astNode));
184 }
185}
186
187function validateTypes(context) {
188 var validateInputObjectCircularRefs = createInputObjectCircularRefsValidator(context);
189 var typeMap = context.schema.getTypeMap();
190
191 for (var _i8 = 0, _objectValues2 = (0, _objectValues5.default)(typeMap); _i8 < _objectValues2.length; _i8++) {
192 var type = _objectValues2[_i8];
193
194 // Ensure all provided types are in fact GraphQL type.
195 if (!(0, _definition.isNamedType)(type)) {
196 context.reportError("Expected GraphQL named type but got: ".concat((0, _inspect.default)(type), "."), type.astNode);
197 continue;
198 } // Ensure it is named correctly (excluding introspection types).
199
200
201 if (!(0, _introspection.isIntrospectionType)(type)) {
202 validateName(context, type);
203 }
204
205 if ((0, _definition.isObjectType)(type)) {
206 // Ensure fields are valid
207 validateFields(context, type); // Ensure objects implement the interfaces they claim to.
208
209 validateInterfaces(context, type);
210 } else if ((0, _definition.isInterfaceType)(type)) {
211 // Ensure fields are valid.
212 validateFields(context, type); // Ensure interfaces implement the interfaces they claim to.
213
214 validateInterfaces(context, type);
215 } else if ((0, _definition.isUnionType)(type)) {
216 // Ensure Unions include valid member types.
217 validateUnionMembers(context, type);
218 } else if ((0, _definition.isEnumType)(type)) {
219 // Ensure Enums have valid values.
220 validateEnumValues(context, type);
221 } else if ((0, _definition.isInputObjectType)(type)) {
222 // Ensure Input Object fields are valid.
223 validateInputFields(context, type); // Ensure Input Objects do not contain non-nullable circular references
224
225 validateInputObjectCircularRefs(type);
226 }
227 }
228}
229
230function validateFields(context, type) {
231 var fields = (0, _objectValues5.default)(type.getFields()); // Objects and Interfaces both must define one or more fields.
232
233 if (fields.length === 0) {
234 context.reportError("Type ".concat(type.name, " must define one or more fields."), getAllNodes(type));
235 }
236
237 for (var _i10 = 0; _i10 < fields.length; _i10++) {
238 var field = fields[_i10];
239 // Ensure they are named correctly.
240 validateName(context, field); // Ensure the type is an output type
241
242 if (!(0, _definition.isOutputType)(field.type)) {
243 var _field$astNode;
244
245 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);
246 } // Ensure the arguments are valid
247
248
249 for (var _i12 = 0, _field$args2 = field.args; _i12 < _field$args2.length; _i12++) {
250 var arg = _field$args2[_i12];
251 var argName = arg.name; // Ensure they are named correctly.
252
253 validateName(context, arg); // Ensure the type is an input type
254
255 if (!(0, _definition.isInputType)(arg.type)) {
256 var _arg$astNode2;
257
258 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$astNode2 = arg.astNode) === null || _arg$astNode2 === void 0 ? void 0 : _arg$astNode2.type);
259 }
260
261 if ((0, _definition.isRequiredArgument)(arg) && arg.deprecationReason != null) {
262 var _arg$astNode3;
263
264 context.reportError("Required argument ".concat(type.name, ".").concat(field.name, "(").concat(argName, ":) cannot be deprecated."), [getDeprecatedDirectiveNode(arg.astNode), // istanbul ignore next (TODO need to write coverage tests)
265 (_arg$astNode3 = arg.astNode) === null || _arg$astNode3 === void 0 ? void 0 : _arg$astNode3.type]);
266 }
267 }
268 }
269}
270
271function validateInterfaces(context, type) {
272 var ifaceTypeNames = Object.create(null);
273
274 for (var _i14 = 0, _type$getInterfaces2 = type.getInterfaces(); _i14 < _type$getInterfaces2.length; _i14++) {
275 var iface = _type$getInterfaces2[_i14];
276
277 if (!(0, _definition.isInterfaceType)(iface)) {
278 context.reportError("Type ".concat((0, _inspect.default)(type), " must only implement Interface types, ") + "it cannot implement ".concat((0, _inspect.default)(iface), "."), getAllImplementsInterfaceNodes(type, iface));
279 continue;
280 }
281
282 if (type === iface) {
283 context.reportError("Type ".concat(type.name, " cannot implement itself because it would create a circular reference."), getAllImplementsInterfaceNodes(type, iface));
284 continue;
285 }
286
287 if (ifaceTypeNames[iface.name]) {
288 context.reportError("Type ".concat(type.name, " can only implement ").concat(iface.name, " once."), getAllImplementsInterfaceNodes(type, iface));
289 continue;
290 }
291
292 ifaceTypeNames[iface.name] = true;
293 validateTypeImplementsAncestors(context, type, iface);
294 validateTypeImplementsInterface(context, type, iface);
295 }
296}
297
298function validateTypeImplementsInterface(context, type, iface) {
299 var typeFieldMap = type.getFields(); // Assert each interface field is implemented.
300
301 for (var _i16 = 0, _objectValues4 = (0, _objectValues5.default)(iface.getFields()); _i16 < _objectValues4.length; _i16++) {
302 var ifaceField = _objectValues4[_i16];
303 var fieldName = ifaceField.name;
304 var typeField = typeFieldMap[fieldName]; // Assert interface field exists on type.
305
306 if (!typeField) {
307 context.reportError("Interface field ".concat(iface.name, ".").concat(fieldName, " expected but ").concat(type.name, " does not provide it."), [ifaceField.astNode].concat(getAllNodes(type)));
308 continue;
309 } // Assert interface field type is satisfied by type field type, by being
310 // a valid subtype. (covariant)
311
312
313 if (!(0, _typeComparators.isTypeSubTypeOf)(context.schema, typeField.type, ifaceField.type)) {
314 var _ifaceField$astNode, _typeField$astNode;
315
316 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), "."), [// istanbul ignore next (TODO need to write coverage tests)
317 (_ifaceField$astNode = ifaceField.astNode) === null || _ifaceField$astNode === void 0 ? void 0 : _ifaceField$astNode.type, // istanbul ignore next (TODO need to write coverage tests)
318 (_typeField$astNode = typeField.astNode) === null || _typeField$astNode === void 0 ? void 0 : _typeField$astNode.type]);
319 } // Assert each interface field arg is implemented.
320
321
322 var _loop = function _loop(_i18, _ifaceField$args2) {
323 var ifaceArg = _ifaceField$args2[_i18];
324 var argName = ifaceArg.name;
325 var typeArg = (0, _find.default)(typeField.args, function (arg) {
326 return arg.name === argName;
327 }); // Assert interface field arg exists on object field.
328
329 if (!typeArg) {
330 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]);
331 return "continue";
332 } // Assert interface field arg type matches object field arg type.
333 // (invariant)
334 // TODO: change to contravariant?
335
336
337 if (!(0, _typeComparators.isEqualType)(ifaceArg.type, typeArg.type)) {
338 var _ifaceArg$astNode, _typeArg$astNode;
339
340 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), "."), [// istanbul ignore next (TODO need to write coverage tests)
341 (_ifaceArg$astNode = ifaceArg.astNode) === null || _ifaceArg$astNode === void 0 ? void 0 : _ifaceArg$astNode.type, // istanbul ignore next (TODO need to write coverage tests)
342 (_typeArg$astNode = typeArg.astNode) === null || _typeArg$astNode === void 0 ? void 0 : _typeArg$astNode.type]);
343 } // TODO: validate default values?
344
345 };
346
347 for (var _i18 = 0, _ifaceField$args2 = ifaceField.args; _i18 < _ifaceField$args2.length; _i18++) {
348 var _ret = _loop(_i18, _ifaceField$args2);
349
350 if (_ret === "continue") continue;
351 } // Assert additional arguments must not be required.
352
353
354 var _loop2 = function _loop2(_i20, _typeField$args2) {
355 var typeArg = _typeField$args2[_i20];
356 var argName = typeArg.name;
357 var ifaceArg = (0, _find.default)(ifaceField.args, function (arg) {
358 return arg.name === argName;
359 });
360
361 if (!ifaceArg && (0, _definition.isRequiredArgument)(typeArg)) {
362 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]);
363 }
364 };
365
366 for (var _i20 = 0, _typeField$args2 = typeField.args; _i20 < _typeField$args2.length; _i20++) {
367 _loop2(_i20, _typeField$args2);
368 }
369 }
370}
371
372function validateTypeImplementsAncestors(context, type, iface) {
373 var ifaceInterfaces = type.getInterfaces();
374
375 for (var _i22 = 0, _iface$getInterfaces2 = iface.getInterfaces(); _i22 < _iface$getInterfaces2.length; _i22++) {
376 var transitive = _iface$getInterfaces2[_i22];
377
378 if (ifaceInterfaces.indexOf(transitive) === -1) {
379 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)));
380 }
381 }
382}
383
384function validateUnionMembers(context, union) {
385 var memberTypes = union.getTypes();
386
387 if (memberTypes.length === 0) {
388 context.reportError("Union type ".concat(union.name, " must define one or more member types."), getAllNodes(union));
389 }
390
391 var includedTypeNames = Object.create(null);
392
393 for (var _i24 = 0; _i24 < memberTypes.length; _i24++) {
394 var memberType = memberTypes[_i24];
395
396 if (includedTypeNames[memberType.name]) {
397 context.reportError("Union type ".concat(union.name, " can only include type ").concat(memberType.name, " once."), getUnionMemberTypeNodes(union, memberType.name));
398 continue;
399 }
400
401 includedTypeNames[memberType.name] = true;
402
403 if (!(0, _definition.isObjectType)(memberType)) {
404 context.reportError("Union type ".concat(union.name, " can only include Object types, ") + "it cannot include ".concat((0, _inspect.default)(memberType), "."), getUnionMemberTypeNodes(union, String(memberType)));
405 }
406 }
407}
408
409function validateEnumValues(context, enumType) {
410 var enumValues = enumType.getValues();
411
412 if (enumValues.length === 0) {
413 context.reportError("Enum type ".concat(enumType.name, " must define one or more values."), getAllNodes(enumType));
414 }
415
416 for (var _i26 = 0; _i26 < enumValues.length; _i26++) {
417 var enumValue = enumValues[_i26];
418 var valueName = enumValue.name; // Ensure valid name.
419
420 validateName(context, enumValue);
421
422 if (valueName === 'true' || valueName === 'false' || valueName === 'null') {
423 context.reportError("Enum type ".concat(enumType.name, " cannot include value: ").concat(valueName, "."), enumValue.astNode);
424 }
425 }
426}
427
428function validateInputFields(context, inputObj) {
429 var fields = (0, _objectValues5.default)(inputObj.getFields());
430
431 if (fields.length === 0) {
432 context.reportError("Input Object type ".concat(inputObj.name, " must define one or more fields."), getAllNodes(inputObj));
433 } // Ensure the arguments are valid
434
435
436 for (var _i28 = 0; _i28 < fields.length; _i28++) {
437 var field = fields[_i28];
438 // Ensure they are named correctly.
439 validateName(context, field); // Ensure the type is an input type
440
441 if (!(0, _definition.isInputType)(field.type)) {
442 var _field$astNode2;
443
444 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);
445 }
446
447 if ((0, _definition.isRequiredInputField)(field) && field.deprecationReason != null) {
448 var _field$astNode3;
449
450 context.reportError("Required input field ".concat(inputObj.name, ".").concat(field.name, " cannot be deprecated."), [getDeprecatedDirectiveNode(field.astNode), // istanbul ignore next (TODO need to write coverage tests)
451 (_field$astNode3 = field.astNode) === null || _field$astNode3 === void 0 ? void 0 : _field$astNode3.type]);
452 }
453 }
454}
455
456function createInputObjectCircularRefsValidator(context) {
457 // Modified copy of algorithm from 'src/validation/rules/NoFragmentCycles.js'.
458 // Tracks already visited types to maintain O(N) and to ensure that cycles
459 // are not redundantly reported.
460 var visitedTypes = Object.create(null); // Array of types nodes used to produce meaningful errors
461
462 var fieldPath = []; // Position in the type path
463
464 var fieldPathIndexByTypeName = Object.create(null);
465 return detectCycleRecursive; // This does a straight-forward DFS to find cycles.
466 // It does not terminate when a cycle was found but continues to explore
467 // the graph to find all possible cycles.
468
469 function detectCycleRecursive(inputObj) {
470 if (visitedTypes[inputObj.name]) {
471 return;
472 }
473
474 visitedTypes[inputObj.name] = true;
475 fieldPathIndexByTypeName[inputObj.name] = fieldPath.length;
476 var fields = (0, _objectValues5.default)(inputObj.getFields());
477
478 for (var _i30 = 0; _i30 < fields.length; _i30++) {
479 var field = fields[_i30];
480
481 if ((0, _definition.isNonNullType)(field.type) && (0, _definition.isInputObjectType)(field.type.ofType)) {
482 var fieldType = field.type.ofType;
483 var cycleIndex = fieldPathIndexByTypeName[fieldType.name];
484 fieldPath.push(field);
485
486 if (cycleIndex === undefined) {
487 detectCycleRecursive(fieldType);
488 } else {
489 var cyclePath = fieldPath.slice(cycleIndex);
490 var pathStr = cyclePath.map(function (fieldObj) {
491 return fieldObj.name;
492 }).join('.');
493 context.reportError("Cannot reference Input Object \"".concat(fieldType.name, "\" within itself through a series of non-null fields: \"").concat(pathStr, "\"."), cyclePath.map(function (fieldObj) {
494 return fieldObj.astNode;
495 }));
496 }
497
498 fieldPath.pop();
499 }
500 }
501
502 fieldPathIndexByTypeName[inputObj.name] = undefined;
503 }
504}
505
506function getAllNodes(object) {
507 var astNode = object.astNode,
508 extensionASTNodes = object.extensionASTNodes;
509 return astNode ? extensionASTNodes ? [astNode].concat(extensionASTNodes) : [astNode] : extensionASTNodes !== null && extensionASTNodes !== void 0 ? extensionASTNodes : [];
510}
511
512function getAllSubNodes(object, getter) {
513 var subNodes = [];
514
515 for (var _i32 = 0, _getAllNodes2 = getAllNodes(object); _i32 < _getAllNodes2.length; _i32++) {
516 var _getter;
517
518 var node = _getAllNodes2[_i32];
519 // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
520 subNodes = subNodes.concat((_getter = getter(node)) !== null && _getter !== void 0 ? _getter : []);
521 }
522
523 return subNodes;
524}
525
526function getAllImplementsInterfaceNodes(type, iface) {
527 return getAllSubNodes(type, function (typeNode) {
528 return typeNode.interfaces;
529 }).filter(function (ifaceNode) {
530 return ifaceNode.name.value === iface.name;
531 });
532}
533
534function getUnionMemberTypeNodes(union, typeName) {
535 return getAllSubNodes(union, function (unionNode) {
536 return unionNode.types;
537 }).filter(function (typeNode) {
538 return typeNode.name.value === typeName;
539 });
540}
541
542function getDeprecatedDirectiveNode(definitionNode) {
543 var _definitionNode$direc;
544
545 // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
546 return definitionNode === null || definitionNode === void 0 ? void 0 : (_definitionNode$direc = definitionNode.directives) === null || _definitionNode$direc === void 0 ? void 0 : _definitionNode$direc.find(function (node) {
547 return node.name.value === _directives.GraphQLDeprecatedDirective.name;
548 });
549}