2015-07-15 23:05:46 +06:00
|
|
|
<?php
|
|
|
|
namespace GraphQL\Validator\Rules;
|
|
|
|
|
|
|
|
|
|
|
|
use GraphQL\Error;
|
|
|
|
use GraphQL\Language\AST\FragmentSpread;
|
|
|
|
use GraphQL\Language\AST\InlineFragment;
|
|
|
|
use GraphQL\Language\AST\Node;
|
|
|
|
use GraphQL\Type\Definition\InterfaceType;
|
|
|
|
use GraphQL\Type\Definition\ObjectType;
|
|
|
|
use GraphQL\Type\Definition\Type;
|
|
|
|
use GraphQL\Type\Definition\UnionType;
|
|
|
|
use GraphQL\Utils;
|
|
|
|
use GraphQL\Validator\ValidationContext;
|
|
|
|
|
|
|
|
class PossibleFragmentSpreads
|
|
|
|
{
|
2015-08-17 20:01:55 +06:00
|
|
|
static function typeIncompatibleSpreadMessage($fragName, $parentType, $fragType)
|
|
|
|
{
|
|
|
|
return "Fragment \"$fragName\" cannot be spread here as objects of type \"$parentType\" can never be of type \"$fragType\".";
|
|
|
|
}
|
|
|
|
|
|
|
|
static function typeIncompatibleAnonSpreadMessage($parentType, $fragType)
|
|
|
|
{
|
|
|
|
return "Fragment cannot be spread here as objects of type \"$parentType\" can never be of type \"$fragType\".";
|
|
|
|
}
|
|
|
|
|
2015-07-15 23:05:46 +06:00
|
|
|
public function __invoke(ValidationContext $context)
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
Node::INLINE_FRAGMENT => function(InlineFragment $node) use ($context) {
|
2015-08-17 20:01:55 +06:00
|
|
|
$fragType = Type::getNamedType($context->getType());
|
2015-07-15 23:05:46 +06:00
|
|
|
$parentType = $context->getParentType();
|
|
|
|
if ($fragType && $parentType && !$this->doTypesOverlap($fragType, $parentType)) {
|
|
|
|
return new Error(
|
2015-08-17 20:01:55 +06:00
|
|
|
self::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
|
2015-07-15 23:05:46 +06:00
|
|
|
[$node]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Node::FRAGMENT_SPREAD => function(FragmentSpread $node) use ($context) {
|
|
|
|
$fragName = $node->name->value;
|
2015-08-17 20:01:55 +06:00
|
|
|
$fragType = Type::getNamedType($this->getFragmentType($context, $fragName));
|
2015-07-15 23:05:46 +06:00
|
|
|
$parentType = $context->getParentType();
|
|
|
|
|
|
|
|
if ($fragType && $parentType && !$this->doTypesOverlap($fragType, $parentType)) {
|
|
|
|
return new Error(
|
2015-08-17 20:01:55 +06:00
|
|
|
self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
|
2015-07-15 23:05:46 +06:00
|
|
|
[$node]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getFragmentType(ValidationContext $context, $name)
|
|
|
|
{
|
|
|
|
$frag = $context->getFragment($name);
|
2015-08-17 20:01:55 +06:00
|
|
|
return $frag ? Utils\TypeInfo::typeFromAST($context->getSchema(), $frag->typeCondition) : null;
|
2015-07-15 23:05:46 +06:00
|
|
|
}
|
|
|
|
|
|
|
|
private function doTypesOverlap($t1, $t2)
|
|
|
|
{
|
|
|
|
if ($t1 === $t2) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if ($t1 instanceof ObjectType) {
|
|
|
|
if ($t2 instanceof ObjectType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return in_array($t1, $t2->getPossibleTypes());
|
|
|
|
}
|
|
|
|
if ($t1 instanceof InterfaceType || $t1 instanceof UnionType) {
|
|
|
|
if ($t2 instanceof ObjectType) {
|
|
|
|
return in_array($t2, $t1->getPossibleTypes());
|
|
|
|
}
|
|
|
|
$t1TypeNames = Utils::keyMap($t1->getPossibleTypes(), function ($type) {
|
|
|
|
return $type->name;
|
|
|
|
});
|
|
|
|
foreach ($t2->getPossibleTypes() as $type) {
|
|
|
|
if (!empty($t1TypeNames[$type->name])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|