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 _definition = require('./definition');
10
11var _directives = require('./directives');
12
13var _introspection = require('./introspection');
14
15var _schema = require('./schema');
16
17var _find = require('../jsutils/find');
18
19var _find2 = _interopRequireDefault(_find);
20
21var _invariant = require('../jsutils/invariant');
22
23var _invariant2 = _interopRequireDefault(_invariant);
24
25var _GraphQLError = require('../error/GraphQLError');
26
27var _assertValidName = require('../utilities/assertValidName');
28
29var _typeComparators = require('../utilities/typeComparators');
30
31function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
32
33function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /**
34 * Copyright (c) 2015-present, Facebook, Inc.
35 *
36 * This source code is licensed under the MIT license found in the
37 * LICENSE file in the root directory of this source tree.
38 *
39 *
40 */
41
42/**
43 * Implements the "Type Validation" sub-sections of the specification's
44 * "Type System" section.
45 *
46 * Validation runs synchronously, returning an array of encountered errors, or
47 * an empty array if no errors were encountered and the Schema is valid.
48 */
49function validateSchema(schema) {
50 // First check to ensure the provided value is in fact a GraphQLSchema.
51 !(0, _schema.isSchema)(schema) ? (0, _invariant2.default)(0, 'Expected ' + String(schema) + ' to be a GraphQL schema.') : void 0;
52
53 // If this Schema has already been validated, return the previous results.
54 if (schema.__validationErrors) {
55 return schema.__validationErrors;
56 }
57
58 // Validate the schema, producing a list of errors.
59 var context = new SchemaValidationContext(schema);
60 validateRootTypes(context);
61 validateDirectives(context);
62 validateTypes(context);
63
64 // Persist the results of validation before returning to ensure validation
65 // does not run multiple times for this schema.
66 var errors = context.getErrors();
67 schema.__validationErrors = errors;
68 return errors;
69}
70
71/**
72 * Utility function which asserts a schema is valid by throwing an error if
73 * it is invalid.
74 */
75function assertValidSchema(schema) {
76 var errors = validateSchema(schema);
77 if (errors.length !== 0) {
78 throw new Error(errors.map(function (error) {
79 return error.message;
80 }).join('\n\n'));
81 }
82}
83
84var SchemaValidationContext = function () {
85 function SchemaValidationContext(schema) {
86 _classCallCheck(this, SchemaValidationContext);
87
88 this._errors = [];
89 this.schema = schema;
90 }
91
92 SchemaValidationContext.prototype.reportError = function reportError(message, nodes) {
93 var _nodes = (Array.isArray(nodes) ? nodes : [nodes]).filter(Boolean);
94 this.addError(new _GraphQLError.GraphQLError(message, _nodes));
95 };
96
97 SchemaValidationContext.prototype.addError = function addError(error) {
98 this._errors.push(error);
99 };
100
101 SchemaValidationContext.prototype.getErrors = function getErrors() {
102 return this._errors;
103 };
104
105 return SchemaValidationContext;
106}();
107
108function validateRootTypes(context) {
109 var schema = context.schema;
110 var queryType = schema.getQueryType();
111 if (!queryType) {
112 context.reportError('Query root type must be provided.', schema.astNode);
113 } else if (!(0, _definition.isObjectType)(queryType)) {
114 context.reportError('Query root type must be Object type, it cannot be ' + String(queryType) + '.', getOperationTypeNode(schema, queryType, 'query'));
115 }
116
117 var mutationType = schema.getMutationType();
118 if (mutationType && !(0, _definition.isObjectType)(mutationType)) {
119 context.reportError('Mutation root type must be Object type if provided, it cannot be ' + (String(mutationType) + '.'), getOperationTypeNode(schema, mutationType, 'mutation'));
120 }
121
122 var subscriptionType = schema.getSubscriptionType();
123 if (subscriptionType && !(0, _definition.isObjectType)(subscriptionType)) {
124 context.reportError('Subscription root type must be Object type if provided, it cannot be ' + (String(subscriptionType) + '.'), getOperationTypeNode(schema, subscriptionType, 'subscription'));
125 }
126}
127
128function getOperationTypeNode(schema, type, operation) {
129 var astNode = schema.astNode;
130 var operationTypeNode = astNode && astNode.operationTypes.find(function (operationType) {
131 return operationType.operation === operation;
132 });
133 return operationTypeNode ? operationTypeNode.type : type && type.astNode;
134}
135
136function validateDirectives(context) {
137 var directives = context.schema.getDirectives();
138 directives.forEach(function (directive) {
139 // Ensure all directives are in fact GraphQL directives.
140 if (!(0, _directives.isDirective)(directive)) {
141 context.reportError('Expected directive but got: ' + String(directive) + '.', directive && directive.astNode);
142 return;
143 }
144
145 // Ensure they are named correctly.
146 validateName(context, directive);
147
148 // TODO: Ensure proper locations.
149
150 // Ensure the arguments are valid.
151 var argNames = Object.create(null);
152 directive.args.forEach(function (arg) {
153 var argName = arg.name;
154
155 // Ensure they are named correctly.
156 validateName(context, arg);
157
158 // Ensure they are unique per directive.
159 if (argNames[argName]) {
160 context.reportError('Argument @' + directive.name + '(' + argName + ':) can only be defined once.', getAllDirectiveArgNodes(directive, argName));
161 return; // continue loop
162 }
163 argNames[argName] = true;
164
165 // Ensure the type is an input type.
166 if (!(0, _definition.isInputType)(arg.type)) {
167 context.reportError('The type of @' + directive.name + '(' + argName + ':) must be Input Type ' + ('but got: ' + String(arg.type) + '.'), getDirectiveArgTypeNode(directive, argName));
168 }
169 });
170 });
171}
172
173function validateName(context, node) {
174 // Ensure names are valid, however introspection types opt out.
175 var error = (0, _assertValidName.isValidNameError)(node.name, node.astNode || undefined);
176 if (error && !(0, _introspection.isIntrospectionType)(node)) {
177 context.addError(error);
178 }
179}
180
181function validateTypes(context) {
182 var typeMap = context.schema.getTypeMap();
183 Object.keys(typeMap).forEach(function (typeName) {
184 var type = typeMap[typeName];
185
186 // Ensure all provided types are in fact GraphQL type.
187 if (!(0, _definition.isNamedType)(type)) {
188 context.reportError('Expected GraphQL named type but got: ' + String(type) + '.', type && type.astNode);
189 return;
190 }
191
192 // Ensure they are named correctly.
193 validateName(context, type);
194
195 if ((0, _definition.isObjectType)(type)) {
196 // Ensure fields are valid
197 validateFields(context, type);
198
199 // Ensure objects implement the interfaces they claim to.
200 validateObjectInterfaces(context, type);
201 } else if ((0, _definition.isInterfaceType)(type)) {
202 // Ensure fields are valid.
203 validateFields(context, type);
204 } else if ((0, _definition.isUnionType)(type)) {
205 // Ensure Unions include valid member types.
206 validateUnionMembers(context, type);
207 } else if ((0, _definition.isEnumType)(type)) {
208 // Ensure Enums have valid values.
209 validateEnumValues(context, type);
210 } else if ((0, _definition.isInputObjectType)(type)) {
211 // Ensure Input Object fields are valid.
212 validateInputFields(context, type);
213 }
214 });
215}
216
217function validateFields(context, type) {
218 var fieldMap = type.getFields();
219 var fieldNames = Object.keys(fieldMap);
220
221 // Objects and Interfaces both must define one or more fields.
222 if (fieldNames.length === 0) {
223 context.reportError('Type ' + type.name + ' must define one or more fields.', getAllObjectOrInterfaceNodes(type));
224 }
225
226 fieldNames.forEach(function (fieldName) {
227 var field = fieldMap[fieldName];
228
229 // Ensure they are named correctly.
230 validateName(context, field);
231
232 // Ensure they were defined at most once.
233 var fieldNodes = getAllFieldNodes(type, fieldName);
234 if (fieldNodes.length > 1) {
235 context.reportError('Field ' + type.name + '.' + fieldName + ' can only be defined once.', fieldNodes);
236 return; // continue loop
237 }
238
239 // Ensure the type is an output type
240 if (!(0, _definition.isOutputType)(field.type)) {
241 context.reportError('The type of ' + type.name + '.' + fieldName + ' must be Output Type ' + ('but got: ' + String(field.type) + '.'), getFieldTypeNode(type, fieldName));
242 }
243
244 // Ensure the arguments are valid
245 var argNames = Object.create(null);
246 field.args.forEach(function (arg) {
247 var argName = arg.name;
248
249 // Ensure they are named correctly.
250 validateName(context, arg);
251
252 // Ensure they are unique per field.
253 if (argNames[argName]) {
254 context.reportError('Field argument ' + type.name + '.' + fieldName + '(' + argName + ':) can only ' + 'be defined once.', getAllFieldArgNodes(type, fieldName, argName));
255 }
256 argNames[argName] = true;
257
258 // Ensure the type is an input type
259 if (!(0, _definition.isInputType)(arg.type)) {
260 context.reportError('The type of ' + type.name + '.' + fieldName + '(' + argName + ':) must be Input ' + ('Type but got: ' + String(arg.type) + '.'), getFieldArgTypeNode(type, fieldName, argName));
261 }
262 });
263 });
264}
265
266function validateObjectInterfaces(context, object) {
267 var implementedTypeNames = Object.create(null);
268 object.getInterfaces().forEach(function (iface) {
269 if (implementedTypeNames[iface.name]) {
270 context.reportError('Type ' + object.name + ' can only implement ' + iface.name + ' once.', getAllImplementsInterfaceNodes(object, iface));
271 return; // continue loop
272 }
273 implementedTypeNames[iface.name] = true;
274 validateObjectImplementsInterface(context, object, iface);
275 });
276}
277
278function validateObjectImplementsInterface(context, object, iface) {
279 if (!(0, _definition.isInterfaceType)(iface)) {
280 context.reportError('Type ' + String(object) + ' must only implement Interface types, ' + ('it cannot implement ' + String(iface) + '.'), getImplementsInterfaceNode(object, iface));
281 return;
282 }
283
284 var objectFieldMap = object.getFields();
285 var ifaceFieldMap = iface.getFields();
286
287 // Assert each interface field is implemented.
288 Object.keys(ifaceFieldMap).forEach(function (fieldName) {
289 var objectField = objectFieldMap[fieldName];
290 var ifaceField = ifaceFieldMap[fieldName];
291
292 // Assert interface field exists on object.
293 if (!objectField) {
294 context.reportError('Interface field ' + iface.name + '.' + fieldName + ' expected but ' + (object.name + ' does not provide it.'), [getFieldNode(iface, fieldName), object.astNode]);
295 // Continue loop over fields.
296 return;
297 }
298
299 // Assert interface field type is satisfied by object field type, by being
300 // a valid subtype. (covariant)
301 if (!(0, _typeComparators.isTypeSubTypeOf)(context.schema, objectField.type, ifaceField.type)) {
302 context.reportError('Interface field ' + iface.name + '.' + fieldName + ' expects type ' + (String(ifaceField.type) + ' but ' + object.name + '.' + fieldName + ' ') + ('is type ' + String(objectField.type) + '.'), [getFieldTypeNode(iface, fieldName), getFieldTypeNode(object, fieldName)]);
303 }
304
305 // Assert each interface field arg is implemented.
306 ifaceField.args.forEach(function (ifaceArg) {
307 var argName = ifaceArg.name;
308 var objectArg = (0, _find2.default)(objectField.args, function (arg) {
309 return arg.name === argName;
310 });
311
312 // Assert interface field arg exists on object field.
313 if (!objectArg) {
314 context.reportError('Interface field argument ' + iface.name + '.' + fieldName + '(' + argName + ':) ' + ('expected but ' + object.name + '.' + fieldName + ' does not provide it.'), [getFieldArgNode(iface, fieldName, argName), getFieldNode(object, fieldName)]);
315 // Continue loop over arguments.
316 return;
317 }
318
319 // Assert interface field arg type matches object field arg type.
320 // (invariant)
321 // TODO: change to contravariant?
322 if (!(0, _typeComparators.isEqualType)(ifaceArg.type, objectArg.type)) {
323 context.reportError('Interface field argument ' + iface.name + '.' + fieldName + '(' + argName + ':) ' + ('expects type ' + String(ifaceArg.type) + ' but ') + (object.name + '.' + fieldName + '(' + argName + ':) is type ') + (String(objectArg.type) + '.'), [getFieldArgTypeNode(iface, fieldName, argName), getFieldArgTypeNode(object, fieldName, argName)]);
324 }
325
326 // TODO: validate default values?
327 });
328
329 // Assert additional arguments must not be required.
330 objectField.args.forEach(function (objectArg) {
331 var argName = objectArg.name;
332 var ifaceArg = (0, _find2.default)(ifaceField.args, function (arg) {
333 return arg.name === argName;
334 });
335 if (!ifaceArg && (0, _definition.isNonNullType)(objectArg.type)) {
336 context.reportError('Object field argument ' + object.name + '.' + fieldName + '(' + argName + ':) ' + ('is of required type ' + String(objectArg.type) + ' but is not also ') + ('provided by the Interface field ' + iface.name + '.' + fieldName + '.'), [getFieldArgTypeNode(object, fieldName, argName), getFieldNode(iface, fieldName)]);
337 }
338 });
339 });
340}
341
342function validateUnionMembers(context, union) {
343 var memberTypes = union.getTypes();
344
345 if (memberTypes.length === 0) {
346 context.reportError('Union type ' + union.name + ' must define one or more member types.', union.astNode);
347 }
348
349 var includedTypeNames = Object.create(null);
350 memberTypes.forEach(function (memberType) {
351 if (includedTypeNames[memberType.name]) {
352 context.reportError('Union type ' + union.name + ' can only include type ' + (memberType.name + ' once.'), getUnionMemberTypeNodes(union, memberType.name));
353 return; // continue loop
354 }
355 includedTypeNames[memberType.name] = true;
356 if (!(0, _definition.isObjectType)(memberType)) {
357 context.reportError('Union type ' + union.name + ' can only include Object types, ' + ('it cannot include ' + String(memberType) + '.'), getUnionMemberTypeNodes(union, String(memberType)));
358 }
359 });
360}
361
362function validateEnumValues(context, enumType) {
363 var enumValues = enumType.getValues();
364
365 if (enumValues.length === 0) {
366 context.reportError('Enum type ' + enumType.name + ' must define one or more values.', enumType.astNode);
367 }
368
369 enumValues.forEach(function (enumValue) {
370 var valueName = enumValue.name;
371
372 // Ensure no duplicates.
373 var allNodes = getEnumValueNodes(enumType, valueName);
374 if (allNodes && allNodes.length > 1) {
375 context.reportError('Enum type ' + enumType.name + ' can include value ' + valueName + ' only once.', allNodes);
376 }
377
378 // Ensure valid name.
379 validateName(context, enumValue);
380 if (valueName === 'true' || valueName === 'false' || valueName === 'null') {
381 context.reportError('Enum type ' + enumType.name + ' cannot include value: ' + valueName + '.', enumValue.astNode);
382 }
383 });
384}
385
386function validateInputFields(context, inputObj) {
387 var fieldMap = inputObj.getFields();
388 var fieldNames = Object.keys(fieldMap);
389
390 if (fieldNames.length === 0) {
391 context.reportError('Input Object type ' + inputObj.name + ' must define one or more fields.', inputObj.astNode);
392 }
393
394 // Ensure the arguments are valid
395 fieldNames.forEach(function (fieldName) {
396 var field = fieldMap[fieldName];
397
398 // Ensure they are named correctly.
399 validateName(context, field);
400
401 // TODO: Ensure they are unique per field.
402
403 // Ensure the type is an input type
404 if (!(0, _definition.isInputType)(field.type)) {
405 context.reportError('The type of ' + inputObj.name + '.' + fieldName + ' must be Input Type ' + ('but got: ' + String(field.type) + '.'), field.astNode && field.astNode.type);
406 }
407 });
408}
409
410function getAllObjectNodes(type) {
411 return type.astNode ? type.extensionASTNodes ? [type.astNode].concat(type.extensionASTNodes) : [type.astNode] : type.extensionASTNodes || [];
412}
413
414function getAllObjectOrInterfaceNodes(type) {
415 return type.astNode ? type.extensionASTNodes ? [type.astNode].concat(type.extensionASTNodes) : [type.astNode] : type.extensionASTNodes || [];
416}
417
418function getImplementsInterfaceNode(type, iface) {
419 return getAllImplementsInterfaceNodes(type, iface)[0];
420}
421
422function getAllImplementsInterfaceNodes(type, iface) {
423 var implementsNodes = [];
424 var astNodes = getAllObjectNodes(type);
425 for (var i = 0; i < astNodes.length; i++) {
426 var _astNode = astNodes[i];
427 if (_astNode && _astNode.interfaces) {
428 _astNode.interfaces.forEach(function (node) {
429 if (node.name.value === iface.name) {
430 implementsNodes.push(node);
431 }
432 });
433 }
434 }
435 return implementsNodes;
436}
437
438function getFieldNode(type, fieldName) {
439 return getAllFieldNodes(type, fieldName)[0];
440}
441
442function getAllFieldNodes(type, fieldName) {
443 var fieldNodes = [];
444 var astNodes = getAllObjectOrInterfaceNodes(type);
445 for (var i = 0; i < astNodes.length; i++) {
446 var _astNode2 = astNodes[i];
447 if (_astNode2 && _astNode2.fields) {
448 _astNode2.fields.forEach(function (node) {
449 if (node.name.value === fieldName) {
450 fieldNodes.push(node);
451 }
452 });
453 }
454 }
455 return fieldNodes;
456}
457
458function getFieldTypeNode(type, fieldName) {
459 var fieldNode = getFieldNode(type, fieldName);
460 return fieldNode && fieldNode.type;
461}
462
463function getFieldArgNode(type, fieldName, argName) {
464 return getAllFieldArgNodes(type, fieldName, argName)[0];
465}
466
467function getAllFieldArgNodes(type, fieldName, argName) {
468 var argNodes = [];
469 var fieldNode = getFieldNode(type, fieldName);
470 if (fieldNode && fieldNode.arguments) {
471 fieldNode.arguments.forEach(function (node) {
472 if (node.name.value === argName) {
473 argNodes.push(node);
474 }
475 });
476 }
477 return argNodes;
478}
479
480function getFieldArgTypeNode(type, fieldName, argName) {
481 var fieldArgNode = getFieldArgNode(type, fieldName, argName);
482 return fieldArgNode && fieldArgNode.type;
483}
484
485function getAllDirectiveArgNodes(directive, argName) {
486 var argNodes = [];
487 var directiveNode = directive.astNode;
488 if (directiveNode && directiveNode.arguments) {
489 directiveNode.arguments.forEach(function (node) {
490 if (node.name.value === argName) {
491 argNodes.push(node);
492 }
493 });
494 }
495 return argNodes;
496}
497
498function getDirectiveArgTypeNode(directive, argName) {
499 var argNode = getAllDirectiveArgNodes(directive, argName)[0];
500 return argNode && argNode.type;
501}
502
503function getUnionMemberTypeNodes(union, typeName) {
504 return union.astNode && union.astNode.types && union.astNode.types.filter(function (type) {
505 return type.name.value === typeName;
506 });
507}
508
509function getEnumValueNodes(enumType, valueName) {
510 return enumType.astNode && enumType.astNode.values && enumType.astNode.values.filter(function (value) {
511 return value.name.value === valueName;
512 });
513}
\No newline at end of file