UNPKG

graphql

Version:

A Query Language and Runtime which can target any service.

275 lines (227 loc) 8.77 kB
// Flow currently disabled for this file. /** * 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'; var _createClass = require('babel-runtime/helpers/create-class')['default']; var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default']; var _slicedToArray = require('babel-runtime/helpers/sliced-to-array')['default']; var _Object$defineProperty = require('babel-runtime/core-js/object/define-property')['default']; var _Object$keys = require('babel-runtime/core-js/object/keys')['default']; var _Map = require('babel-runtime/core-js/map')['default']; var _Set = require('babel-runtime/core-js/set')['default']; var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default']; _Object$defineProperty(exports, '__esModule', { value: true }); exports['default'] = OverlappingFieldsCanBeMerged; // Field name and reason, or field name and list of sub-conflicts. var _error = require('../../error'); var _errors = require('../errors'); var _languagePrinter = require('../../language/printer'); var _languageKinds = require('../../language/kinds'); var _utilsTypeFromAST = require('../../utils/typeFromAST'); var _utilsTypeFromAST2 = _interopRequireDefault(_utilsTypeFromAST); var _utilsFind = require('../../utils/find'); var _utilsFind2 = _interopRequireDefault(_utilsFind); /** * Overlapping fields can be merged * * A selection set is only valid if all fields (including spreading any * fragments) either correspond to distinct response names or can be merged * without ambiguity. */ function OverlappingFieldsCanBeMerged(context) { var comparedSet = new PairSet(); function findConflicts(fieldMap) { var conflicts = []; _Object$keys(fieldMap).forEach(function (responseName) { var fields = fieldMap[responseName]; if (fields.length > 1) { for (var i = 0; i < fields.length; i++) { for (var j = i; j < fields.length; j++) { var conflict = findConflict(responseName, fields[i], fields[j]); if (conflict) { conflicts.push(conflict); } } } } }); return conflicts; } function findConflict(responseName, pair1, pair2) { var _pair1 = _slicedToArray(pair1, 2); var ast1 = _pair1[0]; var def1 = _pair1[1]; var _pair2 = _slicedToArray(pair2, 2); var ast2 = _pair2[0]; var def2 = _pair2[1]; if (ast1 === ast2 || comparedSet.has(ast1, ast2)) { return; } comparedSet.add(ast1, ast2); var name1 = ast1.name.value; var name2 = ast2.name.value; if (name1 !== name2) { return [[responseName, '' + name1 + ' and ' + name2 + ' are different fields'], [ast1, ast2]]; } var type1 = def1 && def1.type; var type2 = def2 && def2.type; if (!sameType(type1, type2)) { return [[responseName, 'they return differing types ' + type1 + ' and ' + type2], [ast1, ast2]]; } var args1 = ast1.arguments || []; var args2 = ast2.arguments || []; if (!sameNameValuePairs(args1, args2)) { return [[responseName, 'they have differing arguments'], [ast1, ast2]]; } var directives1 = ast1.directives || []; var directives2 = ast2.directives || []; if (!sameNameValuePairs(directives1, directives2)) { return [[responseName, 'they have differing directives'], [ast1, ast2]]; } var selectionSet1 = ast1.selectionSet; var selectionSet2 = ast2.selectionSet; if (selectionSet1 && selectionSet2) { var visitedFragmentNames = {}; var subfieldMap = collectFieldASTsAndDefs(context, type1, selectionSet1, visitedFragmentNames); subfieldMap = collectFieldASTsAndDefs(context, type2, selectionSet2, visitedFragmentNames, subfieldMap); var conflicts = findConflicts(subfieldMap); if (conflicts.length > 0) { return [[responseName, conflicts.map(function (_ref) { var _ref2 = _slicedToArray(_ref, 1); var reason = _ref2[0]; return reason; })], conflicts.reduce(function (list, _ref3) { var _ref32 = _slicedToArray(_ref3, 2); var blameNodes = _ref32[1]; return list.concat(blameNodes); }, [ast1, ast2])]; } } } return { SelectionSet: { // Note: we validate on the reverse traversal so deeper conflicts will be // caught first, for clearer error messages. leave: function leave(selectionSet) { var fieldMap = collectFieldASTsAndDefs(context, context.getType(), selectionSet); var conflicts = findConflicts(fieldMap); if (conflicts.length) { return conflicts.map(function (_ref4) { var _ref42 = _slicedToArray(_ref4, 2); var _ref42$0 = _slicedToArray(_ref42[0], 2); var responseName = _ref42$0[0]; var reason = _ref42$0[1]; var blameNodes = _ref42[1]; return new _error.GraphQLError((0, _errors.fieldsConflictMessage)(responseName, reason), blameNodes); }); } } } }; } function sameNameValuePairs(pairs1, pairs2) { if (pairs1.length !== pairs2.length) { return false; } return pairs1.every(function (pair1) { var pair2 = (0, _utilsFind2['default'])(pairs2, function (pair) { return pair.name.value === pair1.name.value; }); if (!pair2) { return false; } return sameValue(pair1.value, pair2.value); }); } function sameValue(value1, value2) { return !value1 && !value2 || (0, _languagePrinter.print)(value1) === (0, _languagePrinter.print)(value2); } function sameType(type1, type2) { return !type1 && !type2 || String(type1) === String(type2); } /** * Given a selectionSet, adds all of the fields in that selection to * the passed in map of fields, and returns it at the end. * * Note: This is not the same as execution's collectFields because at static * time we do not know what object type will be used, so we unconditionally * spread in all fragments. */ function collectFieldASTsAndDefs(context, parentType, selectionSet, visitedFragmentNames, astAndDefs) { var _visitedFragmentNames = visitedFragmentNames || {}; var _astAndDefs = astAndDefs || {}; for (var i = 0; i < selectionSet.selections.length; i++) { var selection = selectionSet.selections[i]; switch (selection.kind) { case _languageKinds.FIELD: var fieldAST = selection; var fieldName = fieldAST.name.value; var fieldDef = parentType && parentType.getFields && parentType.getFields()[fieldName]; var responseName = fieldAST.alias ? fieldAST.alias.value : fieldName; if (!_astAndDefs[responseName]) { _astAndDefs[responseName] = []; } _astAndDefs[responseName].push([fieldAST, fieldDef]); break; case _languageKinds.INLINE_FRAGMENT: var inlineFragment = selection; _astAndDefs = collectFieldASTsAndDefs(context, (0, _utilsTypeFromAST2['default'])(context.getSchema(), inlineFragment.typeCondition), inlineFragment.selectionSet, _visitedFragmentNames, _astAndDefs); break; case _languageKinds.FRAGMENT_SPREAD: var fragmentSpread = selection; var fragName = fragmentSpread.name.value; if (_visitedFragmentNames[fragName]) { continue; } _visitedFragmentNames[fragName] = true; var fragment = context.getFragment(fragName); if (!fragment) { continue; } _astAndDefs = collectFieldASTsAndDefs(context, (0, _utilsTypeFromAST2['default'])(context.getSchema(), fragment.typeCondition), fragment.selectionSet, _visitedFragmentNames, _astAndDefs); break; } } return _astAndDefs; } /** * A way to keep track of pairs of things when the ordering of the pair does * not matter. We do this by maintaining a sort of double adjacency sets. */ var PairSet = (function () { function PairSet() { _classCallCheck(this, PairSet); this._data = new _Map(); } _createClass(PairSet, [{ key: 'has', value: function has(a, b) { var first = this._data.get(a); return first && first.has(b); } }, { key: 'add', value: function add(a, b) { _pairSetAdd(this._data, a, b); _pairSetAdd(this._data, b, a); } }]); return PairSet; })(); function _pairSetAdd(data, a, b) { var set = data.get(a); if (!set) { set = new _Set(); data.set(a, set); } set.add(b); } module.exports = exports['default'];