1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.validateSchema = validateSchema;
|
7 | exports.assertValidSchema = assertValidSchema;
|
8 |
|
9 | var _definition = require('./definition');
|
10 |
|
11 | var _directives = require('./directives');
|
12 |
|
13 | var _introspection = require('./introspection');
|
14 |
|
15 | var _schema = require('./schema');
|
16 |
|
17 | var _find = require('../jsutils/find');
|
18 |
|
19 | var _find2 = _interopRequireDefault(_find);
|
20 |
|
21 | var _invariant = require('../jsutils/invariant');
|
22 |
|
23 | var _invariant2 = _interopRequireDefault(_invariant);
|
24 |
|
25 | var _GraphQLError = require('../error/GraphQLError');
|
26 |
|
27 | var _assertValidName = require('../utilities/assertValidName');
|
28 |
|
29 | var _typeComparators = require('../utilities/typeComparators');
|
30 |
|
31 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
32 |
|
33 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } |
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | function validateSchema(schema) {
|
50 |
|
51 | !(0, _schema.isSchema)(schema) ? (0, _invariant2.default)(0, 'Expected ' + String(schema) + ' to be a GraphQL schema.') : void 0;
|
52 |
|
53 |
|
54 | if (schema.__validationErrors) {
|
55 | return schema.__validationErrors;
|
56 | }
|
57 |
|
58 |
|
59 | var context = new SchemaValidationContext(schema);
|
60 | validateRootTypes(context);
|
61 | validateDirectives(context);
|
62 | validateTypes(context);
|
63 |
|
64 |
|
65 |
|
66 | var errors = context.getErrors();
|
67 | schema.__validationErrors = errors;
|
68 | return errors;
|
69 | }
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | function 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 |
|
84 | var 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 |
|
108 | function 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 |
|
128 | function 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 |
|
136 | function validateDirectives(context) {
|
137 | var directives = context.schema.getDirectives();
|
138 | directives.forEach(function (directive) {
|
139 |
|
140 | if (!(0, _directives.isDirective)(directive)) {
|
141 | context.reportError('Expected directive but got: ' + String(directive) + '.', directive && directive.astNode);
|
142 | return;
|
143 | }
|
144 |
|
145 |
|
146 | validateName(context, directive);
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | var argNames = Object.create(null);
|
152 | directive.args.forEach(function (arg) {
|
153 | var argName = arg.name;
|
154 |
|
155 |
|
156 | validateName(context, arg);
|
157 |
|
158 |
|
159 | if (argNames[argName]) {
|
160 | context.reportError('Argument @' + directive.name + '(' + argName + ':) can only be defined once.', getAllDirectiveArgNodes(directive, argName));
|
161 | return;
|
162 | }
|
163 | argNames[argName] = true;
|
164 |
|
165 |
|
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 |
|
173 | function validateName(context, node) {
|
174 |
|
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 |
|
181 | function validateTypes(context) {
|
182 | var typeMap = context.schema.getTypeMap();
|
183 | Object.keys(typeMap).forEach(function (typeName) {
|
184 | var type = typeMap[typeName];
|
185 |
|
186 |
|
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 |
|
193 | validateName(context, type);
|
194 |
|
195 | if ((0, _definition.isObjectType)(type)) {
|
196 |
|
197 | validateFields(context, type);
|
198 |
|
199 |
|
200 | validateObjectInterfaces(context, type);
|
201 | } else if ((0, _definition.isInterfaceType)(type)) {
|
202 |
|
203 | validateFields(context, type);
|
204 | } else if ((0, _definition.isUnionType)(type)) {
|
205 |
|
206 | validateUnionMembers(context, type);
|
207 | } else if ((0, _definition.isEnumType)(type)) {
|
208 |
|
209 | validateEnumValues(context, type);
|
210 | } else if ((0, _definition.isInputObjectType)(type)) {
|
211 |
|
212 | validateInputFields(context, type);
|
213 | }
|
214 | });
|
215 | }
|
216 |
|
217 | function validateFields(context, type) {
|
218 | var fieldMap = type.getFields();
|
219 | var fieldNames = Object.keys(fieldMap);
|
220 |
|
221 |
|
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 |
|
230 | validateName(context, field);
|
231 |
|
232 |
|
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;
|
237 | }
|
238 |
|
239 |
|
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 |
|
245 | var argNames = Object.create(null);
|
246 | field.args.forEach(function (arg) {
|
247 | var argName = arg.name;
|
248 |
|
249 |
|
250 | validateName(context, arg);
|
251 |
|
252 |
|
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 |
|
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 |
|
266 | function 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;
|
272 | }
|
273 | implementedTypeNames[iface.name] = true;
|
274 | validateObjectImplementsInterface(context, object, iface);
|
275 | });
|
276 | }
|
277 |
|
278 | function 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 |
|
288 | Object.keys(ifaceFieldMap).forEach(function (fieldName) {
|
289 | var objectField = objectFieldMap[fieldName];
|
290 | var ifaceField = ifaceFieldMap[fieldName];
|
291 |
|
292 |
|
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 |
|
296 | return;
|
297 | }
|
298 |
|
299 |
|
300 |
|
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 |
|
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 |
|
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 |
|
316 | return;
|
317 | }
|
318 |
|
319 |
|
320 |
|
321 |
|
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 |
|
327 | });
|
328 |
|
329 |
|
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 |
|
342 | function 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;
|
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 |
|
362 | function 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 |
|
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 |
|
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 |
|
386 | function 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 |
|
395 | fieldNames.forEach(function (fieldName) {
|
396 | var field = fieldMap[fieldName];
|
397 |
|
398 |
|
399 | validateName(context, field);
|
400 |
|
401 |
|
402 |
|
403 |
|
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 |
|
410 | function getAllObjectNodes(type) {
|
411 | return type.astNode ? type.extensionASTNodes ? [type.astNode].concat(type.extensionASTNodes) : [type.astNode] : type.extensionASTNodes || [];
|
412 | }
|
413 |
|
414 | function getAllObjectOrInterfaceNodes(type) {
|
415 | return type.astNode ? type.extensionASTNodes ? [type.astNode].concat(type.extensionASTNodes) : [type.astNode] : type.extensionASTNodes || [];
|
416 | }
|
417 |
|
418 | function getImplementsInterfaceNode(type, iface) {
|
419 | return getAllImplementsInterfaceNodes(type, iface)[0];
|
420 | }
|
421 |
|
422 | function 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 |
|
438 | function getFieldNode(type, fieldName) {
|
439 | return getAllFieldNodes(type, fieldName)[0];
|
440 | }
|
441 |
|
442 | function 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 |
|
458 | function getFieldTypeNode(type, fieldName) {
|
459 | var fieldNode = getFieldNode(type, fieldName);
|
460 | return fieldNode && fieldNode.type;
|
461 | }
|
462 |
|
463 | function getFieldArgNode(type, fieldName, argName) {
|
464 | return getAllFieldArgNodes(type, fieldName, argName)[0];
|
465 | }
|
466 |
|
467 | function 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 |
|
480 | function getFieldArgTypeNode(type, fieldName, argName) {
|
481 | var fieldArgNode = getFieldArgNode(type, fieldName, argName);
|
482 | return fieldArgNode && fieldArgNode.type;
|
483 | }
|
484 |
|
485 | function 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 |
|
498 | function getDirectiveArgTypeNode(directive, argName) {
|
499 | var argNode = getAllDirectiveArgNodes(directive, argName)[0];
|
500 | return argNode && argNode.type;
|
501 | }
|
502 |
|
503 | function 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 |
|
509 | function 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 |