92 lines
3.1 KiB
PHP
92 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace Nuwave\Lighthouse\Federation;
|
|
|
|
use GraphQL\Error\InvariantViolation;
|
|
use GraphQL\Language\AST\FieldNode;
|
|
use GraphQL\Language\AST\SelectionSetNode;
|
|
use GraphQL\Type\Definition\ObjectType;
|
|
use GraphQL\Type\Definition\WrappingType;
|
|
use Nuwave\Lighthouse\Events\ValidateSchema;
|
|
use Nuwave\Lighthouse\Exceptions\FederationException;
|
|
use Nuwave\Lighthouse\Federation\Directives\ExtendsDirective;
|
|
use Nuwave\Lighthouse\Federation\Directives\ExternalDirective;
|
|
use Nuwave\Lighthouse\Federation\Directives\KeyDirective;
|
|
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
|
|
use Nuwave\Lighthouse\Schema\DirectiveLocator;
|
|
|
|
class SchemaValidator
|
|
{
|
|
/**
|
|
* @var \Nuwave\Lighthouse\Schema\DirectiveLocator
|
|
*/
|
|
protected $directiveLocator;
|
|
|
|
public function __construct(DirectiveLocator $directiveLocator)
|
|
{
|
|
$this->directiveLocator = $directiveLocator;
|
|
}
|
|
|
|
public function handle(ValidateSchema $validateSchema): void
|
|
{
|
|
$schema = $validateSchema->schema;
|
|
|
|
foreach ($schema->getTypeMap() as $type) {
|
|
if ($type instanceof ObjectType) {
|
|
$this->validateObjectType($type);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function validateObjectType(ObjectType $type): void
|
|
{
|
|
$ast = $type->astNode;
|
|
if ($ast !== null) {
|
|
$directives = $this->directiveLocator->associated($ast);
|
|
|
|
/** @var \Nuwave\Lighthouse\Support\Contracts\Directive $directive */
|
|
foreach ($directives as $directive) {
|
|
if ($directive instanceof KeyDirective) {
|
|
$this->validateKeySelectionSet($directive->fields(), $type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \Nuwave\Lighthouse\Exceptions\FederationException
|
|
*/
|
|
protected function validateKeySelectionSet(SelectionSetNode $selectionSet, ObjectType $type): void
|
|
{
|
|
foreach ($selectionSet->selections as $selection) {
|
|
if (! $selection instanceof FieldNode) {
|
|
throw new FederationException("Must only use field selections in the `fields` argument of @key, got: {$selection->kind}.");
|
|
}
|
|
|
|
try {
|
|
// Throws if the field is not defined
|
|
$field = $type->getField($selection->name->value);
|
|
} catch (InvariantViolation $i) {
|
|
throw new FederationException($i->getMessage(), $i->getCode(), $i);
|
|
}
|
|
|
|
if (
|
|
ASTHelper::hasDirective($type->astNode, ExtendsDirective::NAME)
|
|
&& ! ASTHelper::hasDirective($field->astNode, ExternalDirective::NAME)
|
|
) {
|
|
throw new FederationException("A @key directive on `{$type->name}` specifies the `{$field->name}` field which has no @external directive.");
|
|
}
|
|
|
|
$nestedSelection = $selection->selectionSet;
|
|
if ($nestedSelection !== null) {
|
|
$type = $field->getType();
|
|
if ($type instanceof WrappingType) {
|
|
$type = $type->getWrappedType(true);
|
|
}
|
|
|
|
$this->validateKeySelectionSet($nestedSelection, $type);
|
|
}
|
|
}
|
|
}
|
|
}
|