UNPKG

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