UNPKG

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