UNPKG

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