graphql
Version:
A Query Language and Runtime which can target any service.
272 lines (236 loc) • 8.74 kB
JavaScript
/* @flow */
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
;
/**
* The result of validation. `isValid` is true if validation is successful.
* `errors` is null if no errors occurred, and is a non-empty array if any
* validation errors occurred.
*/
var _createClass = require('babel-runtime/helpers/create-class')['default'];
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
var _Object$defineProperty = require('babel-runtime/core-js/object/define-property')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
var _interopRequireWildcard = require('babel-runtime/helpers/interop-require-wildcard')['default'];
_Object$defineProperty(exports, '__esModule', {
value: true
});
exports.validateDocument = validateDocument;
var _utilsInvariant = require('../utils/invariant');
var _utilsInvariant2 = _interopRequireDefault(_utilsInvariant);
var _error = require('../error');
var _languageVisitor = require('../language/visitor');
var _languageKinds = require('../language/kinds');
var Kind = _interopRequireWildcard(_languageKinds);
var _utilsTypeInfo = require('../utils/TypeInfo');
var _utilsTypeInfo2 = _interopRequireDefault(_utilsTypeInfo);
var _allRules = require('./allRules');
/**
* Implements the "Evaluating requests" section of the spec.
*
* Rules is a list of function which return visitors
* (see the language/visitor API)
*
* Visitors are expected to return Error objects when invalid.
*
* Visitors can also supply `visitSpreadFragments: true` which will alter the
* behavior of the visitor to skip over top level defined fragments, and instead
* visit those fragments at every point a spread is encountered.
*/
function validateDocument(schema, ast, rules) {
(0, _utilsInvariant2['default'])(schema, 'Must provide schema');
(0, _utilsInvariant2['default'])(ast, 'Must provide document');
var errors = visitUsingRules(schema, ast, rules || _allRules.allRules);
var isValid = errors.length === 0;
return { isValid: isValid, errors: isValid ? null : errors.map(_error.formatError) };
}
/**
* This uses a specialized visitor which runs multiple visitors in parallel,
* while maintaining the visitor skip and break API.
*/
function visitUsingRules(schema, documentAST, rules) {
var typeInfo = new _utilsTypeInfo2['default'](schema);
var context = new ValidationContext(schema, documentAST, typeInfo);
var errors = [];
function visitInstances(ast, instances) {
var skipUntil = Array(instances.length);
var skipCount = 0;
(0, _languageVisitor.visit)(ast, {
enter: function enter(node, key) {
typeInfo.enter(node);
for (var i = 0; i < instances.length; i++) {
// Do not visit this instance if it returned false for a previous node
if (skipUntil[i]) {
continue;
}
var result;
// Do not visit top level fragment definitions if this instance will
// visit those fragments inline because it
// provided `visitSpreadFragments`.
if (node.kind === Kind.FRAGMENT_DEFINITION && key !== undefined && instances[i].visitSpreadFragments) {
result = false;
} else {
var enter = (0, _languageVisitor.getVisitFn)(instances[i], false, node.kind);
result = enter ? enter.apply(instances[i], arguments) : undefined;
}
if (result === false) {
skipUntil[i] = node;
skipCount++;
// If all instances are being skipped over, skip deeper traveral
if (skipCount === instances.length) {
for (var k = 0; k < instances.length; k++) {
if (skipUntil[k] === node) {
skipUntil[k] = null;
skipCount--;
}
}
return false;
}
} else if (result === _languageVisitor.BREAK) {
instances[i] = null;
} else if (result && isError(result)) {
append(errors, result);
for (var j = i - 1; j >= 0; j--) {
var leaveFn = (0, _languageVisitor.getVisitFn)(instances[j], true, node.kind);
if (leaveFn) {
result = leaveFn.apply(instances[j], arguments);
if (result === _languageVisitor.BREAK) {
instances[j] = null;
} else if (isError(result)) {
append(errors, result);
} else if (result !== undefined) {
throw new Error('Validator cannot edit document.');
}
}
}
typeInfo.leave(node);
return false;
} else if (result !== undefined) {
throw new Error('Validator cannot edit document.');
}
}
// If any validation instances provide the flag `visitSpreadFragments`
// and this node is a fragment spread, validate the fragment from
// this point.
if (node.kind === Kind.FRAGMENT_SPREAD) {
var fragment = context.getFragment(node.name.value);
if (fragment) {
var fragVisitingInstances = instances.filter(function (inst, idx) {
return inst.visitSpreadFragments && !skipUntil[idx];
});
if (fragVisitingInstances.length > 0) {
visitInstances(fragment, fragVisitingInstances);
}
}
}
},
leave: function leave(node) {
for (var i = instances.length - 1; i >= 0; i--) {
if (skipUntil[i]) {
if (skipUntil[i] === node) {
skipUntil[i] = null;
skipCount--;
}
continue;
}
var leaveFn = (0, _languageVisitor.getVisitFn)(instances[i], true, node.kind);
if (leaveFn) {
var result = leaveFn.apply(instances[i], arguments);
if (result === _languageVisitor.BREAK) {
instances[i] = null;
} else if (isError(result)) {
append(errors, result);
} else if (result !== undefined) {
throw new Error('Validator cannot edit document.');
}
}
}
typeInfo.leave(node);
}
});
}
// Visit the whole document with instances of all provided rules.
var allRuleInstances = rules.map(function (rule) {
return rule(context);
});
visitInstances(documentAST, allRuleInstances);
return errors;
}
function isError(value) {
return Array.isArray(value) ? value.every(function (item) {
return item instanceof Error;
}) : value instanceof Error;
}
function append(arr, items) {
if (Array.isArray(items)) {
arr.push.apply(arr, items);
} else {
arr.push(items);
}
}
/**
* An instance of this class is passed as the "this" context to all validators,
* allowing access to commonly useful contextual information from within a
* validation rule.
*/
var ValidationContext = (function () {
function ValidationContext(schema, ast, typeInfo) {
_classCallCheck(this, ValidationContext);
this._schema = schema;
this._ast = ast;
this._typeInfo = typeInfo;
}
_createClass(ValidationContext, [{
key: 'getSchema',
value: function getSchema() {
return this._schema;
}
}, {
key: 'getDocument',
value: function getDocument() {
return this._ast;
}
}, {
key: 'getFragment',
value: function getFragment(name) {
var fragments = this._fragments;
if (!fragments) {
this._fragments = fragments = this.getDocument().definitions.reduce(function (frags, statement) {
if (statement.kind === Kind.FRAGMENT_DEFINITION) {
frags[statement.name.value] = statement;
}
return frags;
}, {});
}
return fragments[name];
}
}, {
key: 'getType',
value: function getType() {
return this._typeInfo.getType();
}
}, {
key: 'getParentType',
value: function getParentType() {
return this._typeInfo.getParentType();
}
}, {
key: 'getInputType',
value: function getInputType() {
return this._typeInfo.getInputType();
}
}, {
key: 'getFieldDef',
value: function getFieldDef() {
return this._typeInfo.getFieldDef();
}
}]);
return ValidationContext;
})();
exports.ValidationContext = ValidationContext;