Spaces:
Running
Running
; | |
Object.defineProperty(exports, '__esModule', { | |
value: true, | |
}); | |
exports.OverlappingFieldsCanBeMergedRule = OverlappingFieldsCanBeMergedRule; | |
var _inspect = require('../../jsutils/inspect.js'); | |
var _GraphQLError = require('../../error/GraphQLError.js'); | |
var _kinds = require('../../language/kinds.js'); | |
var _printer = require('../../language/printer.js'); | |
var _definition = require('../../type/definition.js'); | |
var _sortValueNode = require('../../utilities/sortValueNode.js'); | |
var _typeFromAST = require('../../utilities/typeFromAST.js'); | |
function reasonMessage(reason) { | |
if (Array.isArray(reason)) { | |
return reason | |
.map( | |
([responseName, subReason]) => | |
`subfields "${responseName}" conflict because ` + | |
reasonMessage(subReason), | |
) | |
.join(' and '); | |
} | |
return reason; | |
} | |
/** | |
* 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. | |
* | |
* See https://spec.graphql.org/draft/#sec-Field-Selection-Merging | |
*/ | |
function OverlappingFieldsCanBeMergedRule(context) { | |
// A memoization for when fields and a fragment or two fragments are compared | |
// "between" each other for conflicts. Comparisons made be made many times, | |
// so memoizing this can dramatically improve the performance of this validator. | |
const comparedFieldsAndFragmentPairs = new OrderedPairSet(); | |
const comparedFragmentPairs = new PairSet(); // A cache for the "field map" and list of fragment names found in any given | |
// selection set. Selection sets may be asked for this information multiple | |
// times, so this improves the performance of this validator. | |
const cachedFieldsAndFragmentNames = new Map(); | |
return { | |
SelectionSet(selectionSet) { | |
const conflicts = findConflictsWithinSelectionSet( | |
context, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
context.getParentType(), | |
selectionSet, | |
); | |
for (const [[responseName, reason], fields1, fields2] of conflicts) { | |
const reasonMsg = reasonMessage(reason); | |
context.reportError( | |
new _GraphQLError.GraphQLError( | |
`Fields "${responseName}" conflict because ${reasonMsg}. Use different aliases on the fields to fetch both if this was intentional.`, | |
{ | |
nodes: fields1.concat(fields2), | |
}, | |
), | |
); | |
} | |
}, | |
}; | |
} | |
/** | |
* Algorithm: | |
* | |
* Conflicts occur when two fields exist in a query which will produce the same | |
* response name, but represent differing values, thus creating a conflict. | |
* The algorithm below finds all conflicts via making a series of comparisons | |
* between fields. In order to compare as few fields as possible, this makes | |
* a series of comparisons "within" sets of fields and "between" sets of fields. | |
* | |
* Given any selection set, a collection produces both a set of fields by | |
* also including all inline fragments, as well as a list of fragments | |
* referenced by fragment spreads. | |
* | |
* A) Each selection set represented in the document first compares "within" its | |
* collected set of fields, finding any conflicts between every pair of | |
* overlapping fields. | |
* Note: This is the *only time* that a the fields "within" a set are compared | |
* to each other. After this only fields "between" sets are compared. | |
* | |
* B) Also, if any fragment is referenced in a selection set, then a | |
* comparison is made "between" the original set of fields and the | |
* referenced fragment. | |
* | |
* C) Also, if multiple fragments are referenced, then comparisons | |
* are made "between" each referenced fragment. | |
* | |
* D) When comparing "between" a set of fields and a referenced fragment, first | |
* a comparison is made between each field in the original set of fields and | |
* each field in the the referenced set of fields. | |
* | |
* E) Also, if any fragment is referenced in the referenced selection set, | |
* then a comparison is made "between" the original set of fields and the | |
* referenced fragment (recursively referring to step D). | |
* | |
* F) When comparing "between" two fragments, first a comparison is made between | |
* each field in the first referenced set of fields and each field in the the | |
* second referenced set of fields. | |
* | |
* G) Also, any fragments referenced by the first must be compared to the | |
* second, and any fragments referenced by the second must be compared to the | |
* first (recursively referring to step F). | |
* | |
* H) When comparing two fields, if both have selection sets, then a comparison | |
* is made "between" both selection sets, first comparing the set of fields in | |
* the first selection set with the set of fields in the second. | |
* | |
* I) Also, if any fragment is referenced in either selection set, then a | |
* comparison is made "between" the other set of fields and the | |
* referenced fragment. | |
* | |
* J) Also, if two fragments are referenced in both selection sets, then a | |
* comparison is made "between" the two fragments. | |
* | |
*/ | |
// Find all conflicts found "within" a selection set, including those found | |
// via spreading in fragments. Called when visiting each SelectionSet in the | |
// GraphQL Document. | |
function findConflictsWithinSelectionSet( | |
context, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
parentType, | |
selectionSet, | |
) { | |
const conflicts = []; | |
const [fieldMap, fragmentNames] = getFieldsAndFragmentNames( | |
context, | |
cachedFieldsAndFragmentNames, | |
parentType, | |
selectionSet, | |
); // (A) Find find all conflicts "within" the fields of this selection set. | |
// Note: this is the *only place* `collectConflictsWithin` is called. | |
collectConflictsWithin( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
fieldMap, | |
); | |
if (fragmentNames.length !== 0) { | |
// (B) Then collect conflicts between these fields and those represented by | |
// each spread fragment name found. | |
for (let i = 0; i < fragmentNames.length; i++) { | |
collectConflictsBetweenFieldsAndFragment( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
false, | |
fieldMap, | |
fragmentNames[i], | |
); // (C) Then compare this fragment with all other fragments found in this | |
// selection set to collect conflicts between fragments spread together. | |
// This compares each item in the list of fragment names to every other | |
// item in that same list (except for itself). | |
for (let j = i + 1; j < fragmentNames.length; j++) { | |
collectConflictsBetweenFragments( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
false, | |
fragmentNames[i], | |
fragmentNames[j], | |
); | |
} | |
} | |
} | |
return conflicts; | |
} // Collect all conflicts found between a set of fields and a fragment reference | |
// including via spreading in any nested fragments. | |
function collectConflictsBetweenFieldsAndFragment( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fieldMap, | |
fragmentName, | |
) { | |
// Memoize so the fields and fragments are not compared for conflicts more | |
// than once. | |
if ( | |
comparedFieldsAndFragmentPairs.has( | |
fieldMap, | |
fragmentName, | |
areMutuallyExclusive, | |
) | |
) { | |
return; | |
} | |
comparedFieldsAndFragmentPairs.add( | |
fieldMap, | |
fragmentName, | |
areMutuallyExclusive, | |
); | |
const fragment = context.getFragment(fragmentName); | |
if (!fragment) { | |
return; | |
} | |
const [fieldMap2, referencedFragmentNames] = | |
getReferencedFieldsAndFragmentNames( | |
context, | |
cachedFieldsAndFragmentNames, | |
fragment, | |
); // Do not compare a fragment's fieldMap to itself. | |
if (fieldMap === fieldMap2) { | |
return; | |
} // (D) First collect any conflicts between the provided collection of fields | |
// and the collection of fields represented by the given fragment. | |
collectConflictsBetween( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fieldMap, | |
fieldMap2, | |
); // (E) Then collect any conflicts between the provided collection of fields | |
// and any fragment names found in the given fragment. | |
for (const referencedFragmentName of referencedFragmentNames) { | |
collectConflictsBetweenFieldsAndFragment( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fieldMap, | |
referencedFragmentName, | |
); | |
} | |
} // Collect all conflicts found between two fragments, including via spreading in | |
// any nested fragments. | |
function collectConflictsBetweenFragments( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fragmentName1, | |
fragmentName2, | |
) { | |
// No need to compare a fragment to itself. | |
if (fragmentName1 === fragmentName2) { | |
return; | |
} // Memoize so two fragments are not compared for conflicts more than once. | |
if ( | |
comparedFragmentPairs.has( | |
fragmentName1, | |
fragmentName2, | |
areMutuallyExclusive, | |
) | |
) { | |
return; | |
} | |
comparedFragmentPairs.add(fragmentName1, fragmentName2, areMutuallyExclusive); | |
const fragment1 = context.getFragment(fragmentName1); | |
const fragment2 = context.getFragment(fragmentName2); | |
if (!fragment1 || !fragment2) { | |
return; | |
} | |
const [fieldMap1, referencedFragmentNames1] = | |
getReferencedFieldsAndFragmentNames( | |
context, | |
cachedFieldsAndFragmentNames, | |
fragment1, | |
); | |
const [fieldMap2, referencedFragmentNames2] = | |
getReferencedFieldsAndFragmentNames( | |
context, | |
cachedFieldsAndFragmentNames, | |
fragment2, | |
); // (F) First, collect all conflicts between these two collections of fields | |
// (not including any nested fragments). | |
collectConflictsBetween( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fieldMap1, | |
fieldMap2, | |
); // (G) Then collect conflicts between the first fragment and any nested | |
// fragments spread in the second fragment. | |
for (const referencedFragmentName2 of referencedFragmentNames2) { | |
collectConflictsBetweenFragments( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fragmentName1, | |
referencedFragmentName2, | |
); | |
} // (G) Then collect conflicts between the second fragment and any nested | |
// fragments spread in the first fragment. | |
for (const referencedFragmentName1 of referencedFragmentNames1) { | |
collectConflictsBetweenFragments( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
referencedFragmentName1, | |
fragmentName2, | |
); | |
} | |
} // Find all conflicts found between two selection sets, including those found | |
// via spreading in fragments. Called when determining if conflicts exist | |
// between the sub-fields of two overlapping fields. | |
function findConflictsBetweenSubSelectionSets( | |
context, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
parentType1, | |
selectionSet1, | |
parentType2, | |
selectionSet2, | |
) { | |
const conflicts = []; | |
const [fieldMap1, fragmentNames1] = getFieldsAndFragmentNames( | |
context, | |
cachedFieldsAndFragmentNames, | |
parentType1, | |
selectionSet1, | |
); | |
const [fieldMap2, fragmentNames2] = getFieldsAndFragmentNames( | |
context, | |
cachedFieldsAndFragmentNames, | |
parentType2, | |
selectionSet2, | |
); // (H) First, collect all conflicts between these two collections of field. | |
collectConflictsBetween( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fieldMap1, | |
fieldMap2, | |
); // (I) Then collect conflicts between the first collection of fields and | |
// those referenced by each fragment name associated with the second. | |
for (const fragmentName2 of fragmentNames2) { | |
collectConflictsBetweenFieldsAndFragment( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fieldMap1, | |
fragmentName2, | |
); | |
} // (I) Then collect conflicts between the second collection of fields and | |
// those referenced by each fragment name associated with the first. | |
for (const fragmentName1 of fragmentNames1) { | |
collectConflictsBetweenFieldsAndFragment( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fieldMap2, | |
fragmentName1, | |
); | |
} // (J) Also collect conflicts between any fragment names by the first and | |
// fragment names by the second. This compares each item in the first set of | |
// names to each item in the second set of names. | |
for (const fragmentName1 of fragmentNames1) { | |
for (const fragmentName2 of fragmentNames2) { | |
collectConflictsBetweenFragments( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
fragmentName1, | |
fragmentName2, | |
); | |
} | |
} | |
return conflicts; | |
} // Collect all Conflicts "within" one collection of fields. | |
function collectConflictsWithin( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
fieldMap, | |
) { | |
// A field map is a keyed collection, where each key represents a response | |
// name and the value at that key is a list of all fields which provide that | |
// response name. For every response name, if there are multiple fields, they | |
// must be compared to find a potential conflict. | |
for (const [responseName, fields] of Object.entries(fieldMap)) { | |
// This compares every field in the list to every other field in this list | |
// (except to itself). If the list only has one item, nothing needs to | |
// be compared. | |
if (fields.length > 1) { | |
for (let i = 0; i < fields.length; i++) { | |
for (let j = i + 1; j < fields.length; j++) { | |
const conflict = findConflict( | |
context, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
false, // within one collection is never mutually exclusive | |
responseName, | |
fields[i], | |
fields[j], | |
); | |
if (conflict) { | |
conflicts.push(conflict); | |
} | |
} | |
} | |
} | |
} | |
} // Collect all Conflicts between two collections of fields. This is similar to, | |
// but different from the `collectConflictsWithin` function above. This check | |
// assumes that `collectConflictsWithin` has already been called on each | |
// provided collection of fields. This is true because this validator traverses | |
// each individual selection set. | |
function collectConflictsBetween( | |
context, | |
conflicts, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
parentFieldsAreMutuallyExclusive, | |
fieldMap1, | |
fieldMap2, | |
) { | |
// A field map is a keyed collection, where each key represents a response | |
// name and the value at that key is a list of all fields which provide that | |
// response name. For any response name which appears in both provided field | |
// maps, each field from the first field map must be compared to every field | |
// in the second field map to find potential conflicts. | |
for (const [responseName, fields1] of Object.entries(fieldMap1)) { | |
const fields2 = fieldMap2[responseName]; | |
if (fields2) { | |
for (const field1 of fields1) { | |
for (const field2 of fields2) { | |
const conflict = findConflict( | |
context, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
parentFieldsAreMutuallyExclusive, | |
responseName, | |
field1, | |
field2, | |
); | |
if (conflict) { | |
conflicts.push(conflict); | |
} | |
} | |
} | |
} | |
} | |
} // Determines if there is a conflict between two particular fields, including | |
// comparing their sub-fields. | |
function findConflict( | |
context, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
parentFieldsAreMutuallyExclusive, | |
responseName, | |
field1, | |
field2, | |
) { | |
const [parentType1, node1, def1] = field1; | |
const [parentType2, node2, def2] = field2; // If it is known that two fields could not possibly apply at the same | |
// time, due to the parent types, then it is safe to permit them to diverge | |
// in aliased field or arguments used as they will not present any ambiguity | |
// by differing. | |
// It is known that two parent types could never overlap if they are | |
// different Object types. Interface or Union types might overlap - if not | |
// in the current state of the schema, then perhaps in some future version, | |
// thus may not safely diverge. | |
const areMutuallyExclusive = | |
parentFieldsAreMutuallyExclusive || | |
(parentType1 !== parentType2 && | |
(0, _definition.isObjectType)(parentType1) && | |
(0, _definition.isObjectType)(parentType2)); | |
if (!areMutuallyExclusive) { | |
// Two aliases must refer to the same field. | |
const name1 = node1.name.value; | |
const name2 = node2.name.value; | |
if (name1 !== name2) { | |
return [ | |
[responseName, `"${name1}" and "${name2}" are different fields`], | |
[node1], | |
[node2], | |
]; | |
} // Two field calls must have the same arguments. | |
if (!sameArguments(node1, node2)) { | |
return [ | |
[responseName, 'they have differing arguments'], | |
[node1], | |
[node2], | |
]; | |
} | |
} // The return type for each field. | |
const type1 = def1 === null || def1 === void 0 ? void 0 : def1.type; | |
const type2 = def2 === null || def2 === void 0 ? void 0 : def2.type; | |
if (type1 && type2 && doTypesConflict(type1, type2)) { | |
return [ | |
[ | |
responseName, | |
`they return conflicting types "${(0, _inspect.inspect)( | |
type1, | |
)}" and "${(0, _inspect.inspect)(type2)}"`, | |
], | |
[node1], | |
[node2], | |
]; | |
} // Collect and compare sub-fields. Use the same "visited fragment names" list | |
// for both collections so fields in a fragment reference are never | |
// compared to themselves. | |
const selectionSet1 = node1.selectionSet; | |
const selectionSet2 = node2.selectionSet; | |
if (selectionSet1 && selectionSet2) { | |
const conflicts = findConflictsBetweenSubSelectionSets( | |
context, | |
cachedFieldsAndFragmentNames, | |
comparedFieldsAndFragmentPairs, | |
comparedFragmentPairs, | |
areMutuallyExclusive, | |
(0, _definition.getNamedType)(type1), | |
selectionSet1, | |
(0, _definition.getNamedType)(type2), | |
selectionSet2, | |
); | |
return subfieldConflicts(conflicts, responseName, node1, node2); | |
} | |
} | |
function sameArguments(node1, node2) { | |
const args1 = node1.arguments; | |
const args2 = node2.arguments; | |
if (args1 === undefined || args1.length === 0) { | |
return args2 === undefined || args2.length === 0; | |
} | |
if (args2 === undefined || args2.length === 0) { | |
return false; | |
} | |
/* c8 ignore next */ | |
if (args1.length !== args2.length) { | |
/* c8 ignore next */ | |
return false; | |
/* c8 ignore next */ | |
} | |
const values2 = new Map(args2.map(({ name, value }) => [name.value, value])); | |
return args1.every((arg1) => { | |
const value1 = arg1.value; | |
const value2 = values2.get(arg1.name.value); | |
if (value2 === undefined) { | |
return false; | |
} | |
return stringifyValue(value1) === stringifyValue(value2); | |
}); | |
} | |
function stringifyValue(value) { | |
return (0, _printer.print)((0, _sortValueNode.sortValueNode)(value)); | |
} // Two types conflict if both types could not apply to a value simultaneously. | |
// Composite types are ignored as their individual field types will be compared | |
// later recursively. However List and Non-Null types must match. | |
function doTypesConflict(type1, type2) { | |
if ((0, _definition.isListType)(type1)) { | |
return (0, _definition.isListType)(type2) | |
? doTypesConflict(type1.ofType, type2.ofType) | |
: true; | |
} | |
if ((0, _definition.isListType)(type2)) { | |
return true; | |
} | |
if ((0, _definition.isNonNullType)(type1)) { | |
return (0, _definition.isNonNullType)(type2) | |
? doTypesConflict(type1.ofType, type2.ofType) | |
: true; | |
} | |
if ((0, _definition.isNonNullType)(type2)) { | |
return true; | |
} | |
if ( | |
(0, _definition.isLeafType)(type1) || | |
(0, _definition.isLeafType)(type2) | |
) { | |
return type1 !== type2; | |
} | |
return false; | |
} // Given a selection set, return the collection of fields (a mapping of response | |
// name to field nodes and definitions) as well as a list of fragment names | |
// referenced via fragment spreads. | |
function getFieldsAndFragmentNames( | |
context, | |
cachedFieldsAndFragmentNames, | |
parentType, | |
selectionSet, | |
) { | |
const cached = cachedFieldsAndFragmentNames.get(selectionSet); | |
if (cached) { | |
return cached; | |
} | |
const nodeAndDefs = Object.create(null); | |
const fragmentNames = Object.create(null); | |
_collectFieldsAndFragmentNames( | |
context, | |
parentType, | |
selectionSet, | |
nodeAndDefs, | |
fragmentNames, | |
); | |
const result = [nodeAndDefs, Object.keys(fragmentNames)]; | |
cachedFieldsAndFragmentNames.set(selectionSet, result); | |
return result; | |
} // Given a reference to a fragment, return the represented collection of fields | |
// as well as a list of nested fragment names referenced via fragment spreads. | |
function getReferencedFieldsAndFragmentNames( | |
context, | |
cachedFieldsAndFragmentNames, | |
fragment, | |
) { | |
// Short-circuit building a type from the node if possible. | |
const cached = cachedFieldsAndFragmentNames.get(fragment.selectionSet); | |
if (cached) { | |
return cached; | |
} | |
const fragmentType = (0, _typeFromAST.typeFromAST)( | |
context.getSchema(), | |
fragment.typeCondition, | |
); | |
return getFieldsAndFragmentNames( | |
context, | |
cachedFieldsAndFragmentNames, | |
fragmentType, | |
fragment.selectionSet, | |
); | |
} | |
function _collectFieldsAndFragmentNames( | |
context, | |
parentType, | |
selectionSet, | |
nodeAndDefs, | |
fragmentNames, | |
) { | |
for (const selection of selectionSet.selections) { | |
switch (selection.kind) { | |
case _kinds.Kind.FIELD: { | |
const fieldName = selection.name.value; | |
let fieldDef; | |
if ( | |
(0, _definition.isObjectType)(parentType) || | |
(0, _definition.isInterfaceType)(parentType) | |
) { | |
fieldDef = parentType.getFields()[fieldName]; | |
} | |
const responseName = selection.alias | |
? selection.alias.value | |
: fieldName; | |
if (!nodeAndDefs[responseName]) { | |
nodeAndDefs[responseName] = []; | |
} | |
nodeAndDefs[responseName].push([parentType, selection, fieldDef]); | |
break; | |
} | |
case _kinds.Kind.FRAGMENT_SPREAD: | |
fragmentNames[selection.name.value] = true; | |
break; | |
case _kinds.Kind.INLINE_FRAGMENT: { | |
const typeCondition = selection.typeCondition; | |
const inlineFragmentType = typeCondition | |
? (0, _typeFromAST.typeFromAST)(context.getSchema(), typeCondition) | |
: parentType; | |
_collectFieldsAndFragmentNames( | |
context, | |
inlineFragmentType, | |
selection.selectionSet, | |
nodeAndDefs, | |
fragmentNames, | |
); | |
break; | |
} | |
} | |
} | |
} // Given a series of Conflicts which occurred between two sub-fields, generate | |
// a single Conflict. | |
function subfieldConflicts(conflicts, responseName, node1, node2) { | |
if (conflicts.length > 0) { | |
return [ | |
[responseName, conflicts.map(([reason]) => reason)], | |
[node1, ...conflicts.map(([, fields1]) => fields1).flat()], | |
[node2, ...conflicts.map(([, , fields2]) => fields2).flat()], | |
]; | |
} | |
} | |
/** | |
* A way to keep track of pairs of things where the ordering of the pair | |
* matters. | |
* | |
* Provides a third argument for has/set to allow flagging the pair as | |
* weakly or strongly present within the collection. | |
*/ | |
class OrderedPairSet { | |
constructor() { | |
this._data = new Map(); | |
} | |
has(a, b, weaklyPresent) { | |
var _this$_data$get; | |
const result = | |
(_this$_data$get = this._data.get(a)) === null || | |
_this$_data$get === void 0 | |
? void 0 | |
: _this$_data$get.get(b); | |
if (result === undefined) { | |
return false; | |
} | |
return weaklyPresent ? true : weaklyPresent === result; | |
} | |
add(a, b, weaklyPresent) { | |
const map = this._data.get(a); | |
if (map === undefined) { | |
this._data.set(a, new Map([[b, weaklyPresent]])); | |
} else { | |
map.set(b, weaklyPresent); | |
} | |
} | |
} | |
/** | |
* A way to keep track of pairs of similar things when the ordering of the pair | |
* does not matter. | |
*/ | |
class PairSet { | |
constructor() { | |
this._orderedPairSet = new OrderedPairSet(); | |
} | |
has(a, b, weaklyPresent) { | |
return a < b | |
? this._orderedPairSet.has(a, b, weaklyPresent) | |
: this._orderedPairSet.has(b, a, weaklyPresent); | |
} | |
add(a, b, weaklyPresent) { | |
if (a < b) { | |
this._orderedPairSet.add(a, b, weaklyPresent); | |
} else { | |
this._orderedPairSet.add(b, a, weaklyPresent); | |
} | |
} | |
} | |