diff --git a/CHANGELOG.md b/CHANGELOG.md index efd7cbe002..64db0ba20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +## v5.22.2 + +### Fixed + +- Support schema without `Query` type when using federation https://github.com/nuwave/lighthouse/pull/1925 + ## v5.22.1 ### Fixed diff --git a/src/Console/MutationCommand.php b/src/Console/MutationCommand.php index 5342d5a569..32ab865125 100644 --- a/src/Console/MutationCommand.php +++ b/src/Console/MutationCommand.php @@ -2,13 +2,15 @@ namespace Nuwave\Lighthouse\Console; +use Nuwave\Lighthouse\Schema\RootType; + class MutationCommand extends FieldGeneratorCommand { protected $name = 'lighthouse:mutation'; protected $description = 'Create a class for a single field on the root Mutation type.'; - protected $type = 'Mutation'; + protected $type = RootType::MUTATION; protected function namespaceConfigKey(): string { diff --git a/src/Console/QueryCommand.php b/src/Console/QueryCommand.php index fb2c8d00e2..2a6932e3e3 100644 --- a/src/Console/QueryCommand.php +++ b/src/Console/QueryCommand.php @@ -2,13 +2,15 @@ namespace Nuwave\Lighthouse\Console; +use Nuwave\Lighthouse\Schema\RootType; + class QueryCommand extends FieldGeneratorCommand { protected $name = 'lighthouse:query'; protected $description = 'Create a class for a single field on the root Query type.'; - protected $type = 'Query'; + protected $type = RootType::QUERY; protected function namespaceConfigKey(): string { diff --git a/src/Console/SubscriptionCommand.php b/src/Console/SubscriptionCommand.php index 0ea5eb6a4f..0a5da0d2f3 100644 --- a/src/Console/SubscriptionCommand.php +++ b/src/Console/SubscriptionCommand.php @@ -2,13 +2,15 @@ namespace Nuwave\Lighthouse\Console; +use Nuwave\Lighthouse\Schema\RootType; + class SubscriptionCommand extends LighthouseGeneratorCommand { protected $name = 'lighthouse:subscription'; protected $description = 'Create a class for a single field on the root Subscription type.'; - protected $type = 'Subscription'; + protected $type = RootType::SUBSCRIPTION; protected function namespaceConfigKey(): string { diff --git a/src/Federation/ASTManipulator.php b/src/Federation/ASTManipulator.php index 87b87e80a9..f6120b1bc4 100644 --- a/src/Federation/ASTManipulator.php +++ b/src/Federation/ASTManipulator.php @@ -7,6 +7,7 @@ use Nuwave\Lighthouse\Events\ManipulateAST; use Nuwave\Lighthouse\Exceptions\FederationException; use Nuwave\Lighthouse\Schema\AST\DocumentAST; +use Nuwave\Lighthouse\Schema\RootType; class ASTManipulator { @@ -73,8 +74,14 @@ protected function addEntityUnion(DocumentAST &$documentAST): void protected function addRootFields(DocumentAST &$documentAST): void { + // In federation it is fine for a schema to not have a user-defined root query type, + // since we add two federation related fields to it here. + if (! isset($documentAST->types[RootType::QUERY])) { + $documentAST->types[RootType::QUERY] = Parser::objectTypeDefinition(/** @lang GraphQL */ 'type Query'); + } + /** @var \GraphQL\Language\AST\ObjectTypeDefinitionNode $queryType */ - $queryType = $documentAST->types['Query']; + $queryType = $documentAST->types[RootType::QUERY]; $queryType->fields [] = Parser::fieldDefinition(/** @lang GraphQL */ ' _entities( diff --git a/src/Federation/FederationPrinter.php b/src/Federation/FederationPrinter.php index 5363c774dc..7641ab93d9 100644 --- a/src/Federation/FederationPrinter.php +++ b/src/Federation/FederationPrinter.php @@ -21,6 +21,7 @@ use Nuwave\Lighthouse\Federation\Directives\KeyDirective; use Nuwave\Lighthouse\Federation\Directives\ProvidesDirective; use Nuwave\Lighthouse\Federation\Directives\RequiresDirective; +use Nuwave\Lighthouse\Schema\RootType; class FederationPrinter { @@ -53,21 +54,26 @@ public static function print(Schema $schema): string unset($types[$type]); } - $originalQueryType = Arr::pull($types, 'Query'); - $config->setQuery(new ObjectType([ - 'name' => 'Query', - 'fields' => array_filter( - $originalQueryType->getFields(), - static function (FieldDefinition $field): bool { - return ! in_array($field->name, static::FEDERATION_FIELDS); - } - ), - 'interfaces' => $originalQueryType->getInterfaces(), - ])); - - $config->setMutation(Arr::pull($types, 'Mutation')); - - $config->setSubscription(Arr::pull($types, 'Subscription')); + /** @var \GraphQL\Type\Definition\ObjectType $originalQueryType */ + $originalQueryType = Arr::pull($types, RootType::QUERY); + $queryFieldsWithoutFederation = array_filter( + $originalQueryType->getFields(), + static function (FieldDefinition $field): bool { + return ! in_array($field->name, static::FEDERATION_FIELDS); + } + ); + $newQueryType = count($queryFieldsWithoutFederation) > 0 + ? new ObjectType([ + 'name' => RootType::QUERY, + 'fields' => $queryFieldsWithoutFederation, + 'interfaces' => $originalQueryType->getInterfaces(), + ]) + : null; + $config->setQuery($newQueryType); + + $config->setMutation(Arr::pull($types, RootType::MUTATION)); + + $config->setSubscription(Arr::pull($types, RootType::SUBSCRIPTION)); $config->setTypes($types); diff --git a/tests/Integration/Federation/FederationSchemaTest.php b/tests/Integration/Federation/FederationSchemaTest.php index 5bba1d6a24..59c94b0913 100644 --- a/tests/Integration/Federation/FederationSchemaTest.php +++ b/tests/Integration/Federation/FederationSchemaTest.php @@ -41,6 +41,24 @@ public function testServiceQueryShouldReturnValidSdl(): void $this->assertStringContainsString($query, $sdl); } + public function testServiceQueryShouldReturnValidSdlWithoutQuery(): void + { + $foo = /** @lang GraphQL */ <<<'GRAPHQL' +type Foo @key(fields: "id") { + id: ID! @external + foo: String! +} + +GRAPHQL; + + $this->schema = $foo; + + $sdl = $this->_serviceSdl(); + + $this->assertStringContainsString($foo, $sdl); + $this->assertStringNotContainsString(/** @lang GraphQL */ 'type Query', $sdl); + } + public function testFederatedSchemaShouldContainCorrectEntityUnion(): void { $schema = $this->buildSchema(/** @lang GraphQL */ ' diff --git a/tests/Unit/Federation/SchemaBuilderTest.php b/tests/Unit/Federation/SchemaBuilderTest.php index 912ec2cfc3..d0346b2a2e 100644 --- a/tests/Unit/Federation/SchemaBuilderTest.php +++ b/tests/Unit/Federation/SchemaBuilderTest.php @@ -2,8 +2,8 @@ namespace Tests\Unit\Federation; -use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Schema; use Nuwave\Lighthouse\Exceptions\FederationException; use Nuwave\Lighthouse\Federation\FederationServiceProvider; use Tests\TestCase; @@ -37,6 +37,23 @@ public function testFederatedSchema(): void $this->assertTrue($schema->hasType('_Any')); $this->assertTrue($schema->hasType('_FieldSet')); + $this->assertSchemaHasQueryTypeWithFederationFields($schema); + } + + public function testAddsQueryTypeIfNotDefined(): void + { + $schema = $this->buildSchema(/** @lang GraphQL */ ' + type Foo @key(fields: "id") { + id: ID! + foo: String! + } + '); + + $this->assertSchemaHasQueryTypeWithFederationFields($schema); + } + + protected function assertSchemaHasQueryTypeWithFederationFields(Schema $schema): void + { $queryType = $schema->getQueryType(); $this->assertInstanceOf(ObjectType::class, $queryType);