/** * @param ValidationContext $context * @param PairSet $comparedSet * @param $responseName * @param [Field, GraphQLFieldDefinition] $pair1 * @param [Field, GraphQLFieldDefinition] $pair2 * @return array|null */ private function findConflict($responseName, array $pair1, array $pair2, ValidationContext $context, PairSet $comparedSet) { list($ast1, $def1) = $pair1; list($ast2, $def2) = $pair2; if ($ast1 === $ast2 || $comparedSet->has($ast1, $ast2)) { return null; } $comparedSet->add($ast1, $ast2); $name1 = $ast1->name->value; $name2 = $ast2->name->value; if ($name1 !== $name2) { return [[$responseName, "{$name1} and {$name2} are different fields"], [$ast1, $ast2]]; } $type1 = isset($def1) ? $def1->getType() : null; $type2 = isset($def2) ? $def2->getType() : null; if (!$this->sameType($type1, $type2)) { return [[$responseName, "they return differing types {$type1} and {$type2}"], [$ast1, $ast2]]; } $args1 = isset($ast1->arguments) ? $ast1->arguments : []; $args2 = isset($ast2->arguments) ? $ast2->arguments : []; if (!$this->sameNameValuePairs($args1, $args2)) { return [[$responseName, 'they have differing arguments'], [$ast1, $ast2]]; } $directives1 = isset($ast1->directives) ? $ast1->directives : []; $directives2 = isset($ast2->directives) ? $ast2->directives : []; if (!$this->sameNameValuePairs($directives1, $directives2)) { return [[$responseName, 'they have differing directives'], [$ast1, $ast2]]; } $selectionSet1 = isset($ast1->selectionSet) ? $ast1->selectionSet : null; $selectionSet2 = isset($ast2->selectionSet) ? $ast2->selectionSet : null; if ($selectionSet1 && $selectionSet2) { $visitedFragmentNames = new \ArrayObject(); $subfieldMap = $this->collectFieldASTsAndDefs($context, $type1, $selectionSet1, $visitedFragmentNames); $subfieldMap = $this->collectFieldASTsAndDefs($context, $type2, $selectionSet2, $visitedFragmentNames, $subfieldMap); $conflicts = $this->findConflicts($subfieldMap, $context, $comparedSet); if (!empty($conflicts)) { return [[$responseName, array_map(function ($conflict) { return $conflict[0]; }, $conflicts)], array_reduce($conflicts, function ($list, $conflict) { return array_merge($list, $conflict[1]); }, [$ast1, $ast2])]; } } }
/** * @param $parentFieldsAreMutuallyExclusive * @param $responseName * @param [FieldNode, GraphQLFieldDefinition] $pair1 * @param [FieldNode, GraphQLFieldDefinition] $pair2 * @param ValidationContext $context * @return array|null */ private function findConflict($parentFieldsAreMutuallyExclusive, $responseName, array $pair1, array $pair2, ValidationContext $context) { list($parentType1, $ast1, $def1) = $pair1; list($parentType2, $ast2, $def2) = $pair2; // Not a pair. if ($ast1 === $ast2) { return null; } // Memoize, do not report the same issue twice. // Note: Two overlapping ASTs could be encountered both when // `parentFieldsAreMutuallyExclusive` is true and is false, which could // produce different results (when `true` being a subset of `false`). // However we do not need to include this piece of information when // memoizing since this rule visits leaf fields before their parent fields, // ensuring that `parentFieldsAreMutuallyExclusive` is `false` the first // time two overlapping fields are encountered, ensuring that the full // set of validation rules are always checked when necessary. if ($this->comparedSet->has($ast1, $ast2)) { return null; } $this->comparedSet->add($ast1, $ast2); // The return type for each field. $type1 = isset($def1) ? $def1->getType() : null; $type2 = isset($def2) ? $def2->getType() : null; // 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. $fieldsAreMutuallyExclusive = $parentFieldsAreMutuallyExclusive || $parentType1 !== $parentType2 && $parentType1 instanceof ObjectType && $parentType2 instanceof ObjectType; if (!$fieldsAreMutuallyExclusive) { $name1 = $ast1->name->value; $name2 = $ast2->name->value; if ($name1 !== $name2) { return [[$responseName, "{$name1} and {$name2} are different fields"], [$ast1], [$ast2]]; } $args1 = isset($ast1->arguments) ? $ast1->arguments : []; $args2 = isset($ast2->arguments) ? $ast2->arguments : []; if (!$this->sameArguments($args1, $args2)) { return [[$responseName, 'they have differing arguments'], [$ast1], [$ast2]]; } } if ($type1 && $type2 && $this->doTypesConflict($type1, $type2)) { return [[$responseName, "they return conflicting types {$type1} and {$type2}"], [$ast1], [$ast2]]; } $subfieldMap = $this->getSubfieldMap($ast1, $type1, $ast2, $type2, $context); if ($subfieldMap) { $conflicts = $this->findConflicts($fieldsAreMutuallyExclusive, $subfieldMap, $context); return $this->subfieldConflicts($conflicts, $responseName, $ast1, $ast2); } return null; }