UNPKG

graphql

Version:

A Query Language and Runtime which can target any service.

272 lines (236 loc) 8.74 kB
/* @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. */ 'use strict'; /** * 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;