From e4d2f100962d324377bdfed78236a63956bffbe8 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 1 Dec 2021 18:44:11 +0100 Subject: [PATCH 01/20] Add directive `@void` to unify the definition of fields with no return value --- CHANGELOG.md | 6 ++++ docs/master/api-reference/directives.md | 44 +++++++++++++++++++++++++ src/Void/VoidDirective.php | 43 ++++++++++++++++++++++++ src/Void/VoidServiceProvider.php | 44 +++++++++++++++++++++++++ tests/TestCase.php | 2 ++ tests/Unit/Void/VoidDirectiveTest.php | 28 ++++++++++++++++ 6 files changed, 167 insertions(+) create mode 100644 src/Void/VoidDirective.php create mode 100644 src/Void/VoidServiceProvider.php create mode 100644 tests/Unit/Void/VoidDirectiveTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 770c4aaa07..d5e8ee14e1 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.28.0 + +### Added + +- Add directive `@void` to unify the definition of fields with no return value + ## v5.27.1 ### Changed diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index 7208c0d6b5..0a239489dd 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -3142,6 +3142,50 @@ directive @validator( Read more in the [validation docs](../security/validation.md#validator-classes). +## @void + +```graphql +""" +Mark a field that returns no value. + +The return type of the field will be changed to `Unit!`, defined as `enum Unit { UNIT }`. +Whatever result is returned from the resolver will be replaced with `UNIT`. +""" +directive @void on FIELD_DEFINITION +``` + +To enable this directive, add the service provider to your `config/app.php`: + +```php +'providers' => [ + \Nuwave\Lighthouse\Void\VoidServiceProvider::class, +], +``` + +Lighthouse will register the following type in your schema: + +```graphql +"Allows only one value and thus can hold no information." +enum Unit { + "The only possible value." + UNIT +} +``` + +Use this directive on mutations that return no value, see [motivation](https://github.com/graphql/graphql-spec/issues/906). + +```graphql +type Mutation { + fireAndForget: Unit! @void +} +``` + +If your field is defined to return any other type, +Lighthouse will modify the schema definition to have it return `Unit!`. + +Now, no matter what you return from your resolver (or if you return anything at all), +the resulting value will always be `UNIT`. + ## @where ```graphql diff --git a/src/Void/VoidDirective.php b/src/Void/VoidDirective.php new file mode 100644 index 0000000000..f9d5f65487 --- /dev/null +++ b/src/Void/VoidDirective.php @@ -0,0 +1,43 @@ +type = Parser::typeReference(/** @lang GraphQL */ 'Unit!'); + } + + public function handleField(FieldValue $fieldValue, Closure $next): FieldValue + { + $fieldValue->resultHandler(static function (): string { + return VoidServiceProvider::UNIT; + }); + + return $fieldValue; + } +} diff --git a/src/Void/VoidServiceProvider.php b/src/Void/VoidServiceProvider.php new file mode 100644 index 0000000000..9befd49003 --- /dev/null +++ b/src/Void/VoidServiceProvider.php @@ -0,0 +1,44 @@ +listen( + ManipulateAST::class, + static function (ManipulateAST $manipulateAST): void { + $unit = self::UNIT; + $manipulateAST->documentAST->setTypeDefinition( + Parser::enumTypeDefinition(/** @lang GraphQL */ <<listen( + RegisterDirectiveNamespaces::class, + static function (): string { + return __NAMESPACE__; + } + ); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index cdb17b34e7..ad4019d6ec 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,6 +23,7 @@ use Nuwave\Lighthouse\Testing\MocksResolvers; use Nuwave\Lighthouse\Testing\UsesTestSchema; use Nuwave\Lighthouse\Validation\ValidationServiceProvider; +use Nuwave\Lighthouse\Void\VoidServiceProvider; use Orchestra\Testbench\TestCase as BaseTestCase; use Symfony\Component\Console\Tester\CommandTester; use Tests\Utils\Policies\AuthServiceProvider; @@ -78,6 +79,7 @@ protected function getPackageProviders($app): array PaginationServiceProvider::class, SoftDeletesServiceProvider::class, ValidationServiceProvider::class, + VoidServiceProvider::class, ]; } diff --git a/tests/Unit/Void/VoidDirectiveTest.php b/tests/Unit/Void/VoidDirectiveTest.php new file mode 100644 index 0000000000..422e4098d5 --- /dev/null +++ b/tests/Unit/Void/VoidDirectiveTest.php @@ -0,0 +1,28 @@ +schema = /** @lang GraphQL */ ' + type Query { + foo: Int @void + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + foo + } + ')->assertJson([ + 'data' => [ + 'foo' => VoidServiceProvider::UNIT, + ], + ]); + } +} From 9411e185c8dbbc7ec2843eafbb51bcb0ee66b6ab Mon Sep 17 00:00:00 2001 From: spawnia Date: Wed, 1 Dec 2021 17:44:51 +0000 Subject: [PATCH 02/20] Prettify docs --- docs/5/performance/schema-caching.md | 2 +- docs/master/api-reference/directives.md | 2 +- docs/master/performance/schema-caching.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/5/performance/schema-caching.md b/docs/5/performance/schema-caching.md index 249d8f0dbc..38d30c12a0 100644 --- a/docs/5/performance/schema-caching.md +++ b/docs/5/performance/schema-caching.md @@ -14,7 +14,7 @@ using the [cache](../api-reference/commands.md#cache) artisan command: The structure of the serialized schema can change between Lighthouse releases. In order to prevent errors, use cache version 2 and a deployment method that -atomically updates both the cache file and the dependencies, e.g. K8s. +atomically updates both the cache file and the dependencies, e.g. K8s. ## Development diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index 0a239489dd..9bb77198f4 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -3176,7 +3176,7 @@ Use this directive on mutations that return no value, see [motivation](https://g ```graphql type Mutation { - fireAndForget: Unit! @void + fireAndForget: Unit! @void } ``` diff --git a/docs/master/performance/schema-caching.md b/docs/master/performance/schema-caching.md index 249d8f0dbc..38d30c12a0 100644 --- a/docs/master/performance/schema-caching.md +++ b/docs/master/performance/schema-caching.md @@ -14,7 +14,7 @@ using the [cache](../api-reference/commands.md#cache) artisan command: The structure of the serialized schema can change between Lighthouse releases. In order to prevent errors, use cache version 2 and a deployment method that -atomically updates both the cache file and the dependencies, e.g. K8s. +atomically updates both the cache file and the dependencies, e.g. K8s. ## Development From e6c1e53664463f2357c0620e66e4dd67e50abca2 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 2 Dec 2021 14:23:48 +0100 Subject: [PATCH 03/20] add todo --- src/Console/IdeHelperCommand.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Console/IdeHelperCommand.php b/src/Console/IdeHelperCommand.php index 5efa21c8ef..2d7af9705e 100644 --- a/src/Console/IdeHelperCommand.php +++ b/src/Console/IdeHelperCommand.php @@ -133,6 +133,8 @@ public static function schemaDirectivesPath(): string protected function programmaticTypes(TypeRegistry $typeRegistry): void { + // TODO diff SchemaSource vs the final DocumentAST instead + // Users may register types programmatically, e.g. in service providers // In order to allow referencing those in the schema, it is useful to print // those types to a helper schema, excluding types the user defined in the schema From c8e2d2ce3fa81ead39983bad945fd911ab927778 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 2 Dec 2021 20:31:19 +0100 Subject: [PATCH 04/20] Add missing types to programmatic-types.graphql --- src/Console/IdeHelperCommand.php | 48 ++++++++++++++-------- src/Schema/SchemaBuilder.php | 3 +- src/Schema/Source/SchemaSourceProvider.php | 2 +- src/Schema/TypeRegistry.php | 2 +- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/Console/IdeHelperCommand.php b/src/Console/IdeHelperCommand.php index 2d7af9705e..897b599108 100644 --- a/src/Console/IdeHelperCommand.php +++ b/src/Console/IdeHelperCommand.php @@ -2,14 +2,16 @@ namespace Nuwave\Lighthouse\Console; +use GraphQL\Language\AST\TypeDefinitionNode; +use GraphQL\Language\Parser; use GraphQL\Type\Definition\Type; use GraphQL\Utils\SchemaPrinter; use HaydenPierce\ClassFinder\ClassFinder; use Illuminate\Console\Command; -use Illuminate\Support\Collection; use Nuwave\Lighthouse\Schema\AST\ASTHelper; use Nuwave\Lighthouse\Schema\DirectiveLocator; -use Nuwave\Lighthouse\Schema\TypeRegistry; +use Nuwave\Lighthouse\Schema\SchemaBuilder; +use Nuwave\Lighthouse\Schema\Source\SchemaSourceProvider; use Nuwave\Lighthouse\Support\Contracts\Directive; class IdeHelperCommand extends Command @@ -28,10 +30,10 @@ class IdeHelperCommand extends Command protected $description = 'Create IDE helper files to improve type checking and autocompletion.'; - public function handle(DirectiveLocator $directiveLocator, TypeRegistry $typeRegistry): int + public function handle(DirectiveLocator $directiveLocator, SchemaSourceProvider $schemaSourceProvider, SchemaBuilder $schemaBuilder): int { $this->schemaDirectiveDefinitions($directiveLocator); - $this->programmaticTypes($typeRegistry); + $this->programmaticTypes($schemaSourceProvider, $schemaBuilder); $this->phpIdeHelper(); $this->info("\nIt is recommended to add them to your .gitignore file."); @@ -131,28 +133,42 @@ public static function schemaDirectivesPath(): string return base_path().'/schema-directives.graphql'; } - protected function programmaticTypes(TypeRegistry $typeRegistry): void + /** + * Users may register types programmatically, e.g. in service providers. + * In order to allow referencing those in the schema, it is useful to print + * those types to a helper schema, excluding types the user defined in the schema. + */ + protected function programmaticTypes(SchemaSourceProvider $schemaSourceProvider, SchemaBuilder $schemaBuilder): void { - // TODO diff SchemaSource vs the final DocumentAST instead + $sourceSchema = Parser::parse($schemaSourceProvider->getSchemaString()); + $sourceTypes = []; + foreach ($sourceSchema->definitions as $definition) { + if ($definition instanceof TypeDefinitionNode) { + $sourceTypes[$definition->name->value] = true; + } + } + + $allTypes = $schemaBuilder->schema()->getTypeMap(); - // Users may register types programmatically, e.g. in service providers - // In order to allow referencing those in the schema, it is useful to print - // those types to a helper schema, excluding types the user defined in the schema - $types = new Collection($typeRegistry->resolvedTypes()); + $programmaticTypes = array_diff_key($allTypes, $sourceTypes); $filePath = static::programmaticTypesPath(); - if ($types->isEmpty() && file_exists($filePath)) { + if (count($programmaticTypes) === 0 && file_exists($filePath)) { \Safe\unlink($filePath); return; } - $schema = $types - ->map(function (Type $type): string { - return SchemaPrinter::printType($type); - }) - ->implode("\n"); + $schema = implode( + "\n", + array_map( + function (Type $type): string { + return SchemaPrinter::printType($type); + }, + $programmaticTypes + ) + ); \Safe\file_put_contents($filePath, self::GENERATED_NOTICE.$schema); diff --git a/src/Schema/SchemaBuilder.php b/src/Schema/SchemaBuilder.php index 365ee6935e..857d48f951 100644 --- a/src/Schema/SchemaBuilder.php +++ b/src/Schema/SchemaBuilder.php @@ -79,8 +79,7 @@ function (string $name): Type { } ); - // This is just used for introspection, it is required - // to be able to retrieve all the types in the schema + // Enables introspection to list all types in the schema $config->setTypes( /** * @return array diff --git a/src/Schema/Source/SchemaSourceProvider.php b/src/Schema/Source/SchemaSourceProvider.php index 1adc99df6b..b8b7f3739b 100644 --- a/src/Schema/Source/SchemaSourceProvider.php +++ b/src/Schema/Source/SchemaSourceProvider.php @@ -5,7 +5,7 @@ interface SchemaSourceProvider { /** - * Provide the schema definition. + * Provide the string contents of the schema definition. */ public function getSchemaString(): string; } diff --git a/src/Schema/TypeRegistry.php b/src/Schema/TypeRegistry.php index ee86c6e8fa..191e772506 100644 --- a/src/Schema/TypeRegistry.php +++ b/src/Schema/TypeRegistry.php @@ -189,7 +189,7 @@ public function possibleTypes(): array /** * Get the types that are currently resolved. * - * Note that this does not all possible types, only those that + * This does not return all possible types, only those that * are programmatically registered or already resolved. * * @return array From 71e82ca7df1bc9ce82e5ba937fa328128116a766 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 2 Dec 2021 20:32:02 +0100 Subject: [PATCH 05/20] cl --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5e8ee14e1..0b22dc1c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ You can find and compare releases at the [GitHub release page](https://github.co - Add directive `@void` to unify the definition of fields with no return value +### Fixed + +- Add missing types to `programmatic-types.graphql` in artisan command `lighthouse:ide-helper` + ## v5.27.1 ### Changed From 9d2262fd66a1fcf9549c777df93c7c59da3b6979 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 2 Dec 2021 20:43:21 +0100 Subject: [PATCH 06/20] clear --- src/Console/IdeHelperCommand.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Console/IdeHelperCommand.php b/src/Console/IdeHelperCommand.php index 897b599108..b77283577f 100644 --- a/src/Console/IdeHelperCommand.php +++ b/src/Console/IdeHelperCommand.php @@ -8,6 +8,7 @@ use GraphQL\Utils\SchemaPrinter; use HaydenPierce\ClassFinder\ClassFinder; use Illuminate\Console\Command; +use Nuwave\Lighthouse\Schema\AST\ASTCache; use Nuwave\Lighthouse\Schema\AST\ASTHelper; use Nuwave\Lighthouse\Schema\DirectiveLocator; use Nuwave\Lighthouse\Schema\SchemaBuilder; @@ -30,11 +31,11 @@ class IdeHelperCommand extends Command protected $description = 'Create IDE helper files to improve type checking and autocompletion.'; - public function handle(DirectiveLocator $directiveLocator, SchemaSourceProvider $schemaSourceProvider, SchemaBuilder $schemaBuilder): int + public function handle(): int { - $this->schemaDirectiveDefinitions($directiveLocator); - $this->programmaticTypes($schemaSourceProvider, $schemaBuilder); - $this->phpIdeHelper(); + $this->laravel->call([$this, 'schemaDirectiveDefinitions']); + $this->laravel->call([$this, 'programmaticTypes']); + $this->laravel->call([$this, 'phpIdeHelper']); $this->info("\nIt is recommended to add them to your .gitignore file."); @@ -138,7 +139,7 @@ public static function schemaDirectivesPath(): string * In order to allow referencing those in the schema, it is useful to print * those types to a helper schema, excluding types the user defined in the schema. */ - protected function programmaticTypes(SchemaSourceProvider $schemaSourceProvider, SchemaBuilder $schemaBuilder): void + protected function programmaticTypes(SchemaSourceProvider $schemaSourceProvider, ASTCache $astCache, SchemaBuilder $schemaBuilder): void { $sourceSchema = Parser::parse($schemaSourceProvider->getSchemaString()); $sourceTypes = []; @@ -148,6 +149,8 @@ protected function programmaticTypes(SchemaSourceProvider $schemaSourceProvider, } } + $astCache->clear(); + $allTypes = $schemaBuilder->schema()->getTypeMap(); $programmaticTypes = array_diff_key($allTypes, $sourceTypes); From 4e8d70e6679c037b1e96b5cf14e95476ac908c84 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 2 Dec 2021 20:43:36 +0100 Subject: [PATCH 07/20] newline --- src/Console/IdeHelperCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/IdeHelperCommand.php b/src/Console/IdeHelperCommand.php index b77283577f..be5ec730b3 100644 --- a/src/Console/IdeHelperCommand.php +++ b/src/Console/IdeHelperCommand.php @@ -164,7 +164,7 @@ protected function programmaticTypes(SchemaSourceProvider $schemaSourceProvider, } $schema = implode( - "\n", + "\n\n", array_map( function (Type $type): string { return SchemaPrinter::printType($type); From bbe0174208972cb8f447217322e4895a6a983246 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 2 Dec 2021 20:44:44 +0100 Subject: [PATCH 08/20] pub --- src/Console/IdeHelperCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Console/IdeHelperCommand.php b/src/Console/IdeHelperCommand.php index be5ec730b3..0c305bdf4d 100644 --- a/src/Console/IdeHelperCommand.php +++ b/src/Console/IdeHelperCommand.php @@ -45,7 +45,7 @@ public function handle(): int /** * Create and write schema directive definitions to a file. */ - protected function schemaDirectiveDefinitions(DirectiveLocator $directiveLocator): void + public function schemaDirectiveDefinitions(DirectiveLocator $directiveLocator): void { $schema = /** @lang GraphQL */ <<<'GRAPHQL' """ @@ -139,7 +139,7 @@ public static function schemaDirectivesPath(): string * In order to allow referencing those in the schema, it is useful to print * those types to a helper schema, excluding types the user defined in the schema. */ - protected function programmaticTypes(SchemaSourceProvider $schemaSourceProvider, ASTCache $astCache, SchemaBuilder $schemaBuilder): void + public function programmaticTypes(SchemaSourceProvider $schemaSourceProvider, ASTCache $astCache, SchemaBuilder $schemaBuilder): void { $sourceSchema = Parser::parse($schemaSourceProvider->getSchemaString()); $sourceTypes = []; @@ -183,7 +183,7 @@ public static function programmaticTypesPath(): string return base_path().'/programmatic-types.graphql'; } - protected function phpIdeHelper(): void + public function phpIdeHelper(): void { $filePath = static::phpIdeHelperPath(); $contents = \Safe\file_get_contents(__DIR__.'/../../_ide_helper.php'); From 40f73a1c52e8e34a59e2e95377e7eb1d2717d413 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 2 Dec 2021 20:47:22 +0100 Subject: [PATCH 09/20] nl --- src/Console/IdeHelperCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/IdeHelperCommand.php b/src/Console/IdeHelperCommand.php index 0c305bdf4d..4e1a439b87 100644 --- a/src/Console/IdeHelperCommand.php +++ b/src/Console/IdeHelperCommand.php @@ -173,7 +173,7 @@ function (Type $type): string { ) ); - \Safe\file_put_contents($filePath, self::GENERATED_NOTICE.$schema); + \Safe\file_put_contents($filePath, self::GENERATED_NOTICE.$schema."\n"); $this->info("Wrote definitions for programmatically registered types to $filePath."); } From 3fc0c107b838b622b8df19d83a402b453c8dfe88 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 2 Dec 2021 21:03:00 +0100 Subject: [PATCH 10/20] mixin --- CHANGELOG.md | 1 + _ide_helper.php | 10 ++++++++++ src/Testing/TestResponseMixin.php | 13 ++++++++++++ tests/Integration/GraphQLTest.php | 33 +++++++++++++++++-------------- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b22dc1c6a..01c23544e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ You can find and compare releases at the [GitHub release page](https://github.co ### Added - Add directive `@void` to unify the definition of fields with no return value +- Mixin method `assertGraphQLErrorFree()` to `\Illuminate\Testing\TestResponse` ### Fixed diff --git a/_ide_helper.php b/_ide_helper.php index 3c2b6bfc79..ed36ddc1b2 100644 --- a/_ide_helper.php +++ b/_ide_helper.php @@ -63,6 +63,16 @@ public function assertGraphQLErrorMessage(string $message): self return $this; } + /** + * Assert the response contains no errors. + * + * @return $this + */ + public function assertGraphQLErrorFree(): self + { + return $this; + } + /** * Assert the response contains an error from the given category. * diff --git a/src/Testing/TestResponseMixin.php b/src/Testing/TestResponseMixin.php index 6c481290e4..e115cdf9b8 100644 --- a/src/Testing/TestResponseMixin.php +++ b/src/Testing/TestResponseMixin.php @@ -72,6 +72,19 @@ public function assertGraphQLErrorMessage(): Closure }; } + public function assertGraphQLErrorFree(): Closure + { + return function () { + $errors = $this->json('errors'); + Assert::assertNull( + $errors, + 'Expected the GraphQL response to contain no errors, got: '.\Safe\json_encode($errors) + ); + + return $this; + }; + } + public function assertGraphQLErrorCategory(): Closure { return function (string $category) { diff --git a/tests/Integration/GraphQLTest.php b/tests/Integration/GraphQLTest.php index de49be6b38..84e27a326b 100644 --- a/tests/Integration/GraphQLTest.php +++ b/tests/Integration/GraphQLTest.php @@ -23,6 +23,7 @@ public function testResolvesQueryViaPostRequest(): void foo } ') + ->assertGraphQLErrorFree() ->assertExactJson([ 'data' => [ 'foo' => Foo::THE_ANSWER, @@ -54,21 +55,23 @@ public function testResolvesQueryViaGetRequest(): void public function testResolvesNamedOperation(): void { - $this->postGraphQL([ - 'query' => /** @lang GraphQL */ ' - query Foo { - foo - } - query Bar { - bar - } - ', - 'operationName' => 'Bar', - ])->assertExactJson([ - 'data' => [ - 'bar' => Bar::RESULT, - ], - ]); + $this + ->postGraphQL([ + 'query' => /** @lang GraphQL */ ' + query Foo { + foo + } + query Bar { + bar + } + ', + 'operationName' => 'Bar', + ]) + ->assertExactJson([ + 'data' => [ + 'bar' => Bar::RESULT, + ], + ]); } public function testResolveBatchedQueries(): void From 5a131d838345e0bf70d365c0dad227819b3e82d2 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 2 Dec 2021 21:06:33 +0100 Subject: [PATCH 11/20] clarify --- docs/master/api-reference/directives.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index 9bb77198f4..74be00ae5d 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -3176,15 +3176,12 @@ Use this directive on mutations that return no value, see [motivation](https://g ```graphql type Mutation { - fireAndForget: Unit! @void + fireAndForget: _ @void } ``` -If your field is defined to return any other type, -Lighthouse will modify the schema definition to have it return `Unit!`. - -Now, no matter what you return from your resolver (or if you return anything at all), -the resulting value will always be `UNIT`. +Lighthouse will modify the definition of your field and have it return `Unit!`. +No matter what your resolver returns, the resulting value will always be `UNIT`. ## @where From 643b7cc54bb4edbc603a2c0cb2d02c6e3a7cf35c Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 6 Dec 2021 16:04:39 +0100 Subject: [PATCH 12/20] document --- docs/master/api-reference/directives.md | 6 +++++- src/Void/VoidServiceProvider.php | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index 74be00ae5d..dd54114030 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -3165,7 +3165,11 @@ To enable this directive, add the service provider to your `config/app.php`: Lighthouse will register the following type in your schema: ```graphql -"Allows only one value and thus can hold no information." +""" +Allows only one value and thus can hold no information. + +https://en.wikipedia.org/wiki/Unit_type +""" enum Unit { "The only possible value." UNIT diff --git a/src/Void/VoidServiceProvider.php b/src/Void/VoidServiceProvider.php index 9befd49003..cfcbc371ea 100644 --- a/src/Void/VoidServiceProvider.php +++ b/src/Void/VoidServiceProvider.php @@ -23,7 +23,11 @@ static function (ManipulateAST $manipulateAST): void { $unit = self::UNIT; $manipulateAST->documentAST->setTypeDefinition( Parser::enumTypeDefinition(/** @lang GraphQL */ << Date: Mon, 6 Dec 2021 15:05:51 +0000 Subject: [PATCH 13/20] Prettify docs --- docs/5/api-reference/directives.md | 118 ++++++++++++++--------------- docs/5/digging-deeper/ordering.md | 39 ++++------ 2 files changed, 74 insertions(+), 83 deletions(-) diff --git a/docs/5/api-reference/directives.md b/docs/5/api-reference/directives.md index f0fa823e9a..ff0274f669 100644 --- a/docs/5/api-reference/directives.md +++ b/docs/5/api-reference/directives.md @@ -1989,78 +1989,78 @@ type Query { Sort a result list by one or more given columns. """ directive @orderBy( - """ - Restrict the allowed column names to a well-defined list. - This improves introspection capabilities and security. - Mutually exclusive with the `columnsEnum` argument. - Only used when the directive is added on an argument. - """ - columns: [String!] - - """ - Use an existing enumeration type to restrict the allowed columns to a predefined list. - This allowes you to re-use the same enum for multiple fields. - Mutually exclusive with the `columns` argument. - Only used when the directive is added on an argument. - """ - columnsEnum: String - - """ - Allow clients to sort by aggregates on relations. - Only used when the directive is added on an argument. - """ - relations: [OrderByRelation!] - - """ - The database column for which the order by clause will be applied on. - Only used when the directive is added on a field. - """ - column: String - - """ - The direction of the order by clause. - Only used when the directive is added on a field. - """ - direction: OrderByDirection = ASC + """ + Restrict the allowed column names to a well-defined list. + This improves introspection capabilities and security. + Mutually exclusive with the `columnsEnum` argument. + Only used when the directive is added on an argument. + """ + columns: [String!] + + """ + Use an existing enumeration type to restrict the allowed columns to a predefined list. + This allowes you to re-use the same enum for multiple fields. + Mutually exclusive with the `columns` argument. + Only used when the directive is added on an argument. + """ + columnsEnum: String + + """ + Allow clients to sort by aggregates on relations. + Only used when the directive is added on an argument. + """ + relations: [OrderByRelation!] + + """ + The database column for which the order by clause will be applied on. + Only used when the directive is added on a field. + """ + column: String + + """ + The direction of the order by clause. + Only used when the directive is added on a field. + """ + direction: OrderByDirection = ASC ) on ARGUMENT_DEFINITION | FIELD_DEFINITION """ Options for the `direction` argument on `@orderBy`. """ enum OrderByDirection { - """ - Sort in ascending order. - """ - ASC + """ + Sort in ascending order. + """ + ASC - """ - Sort in descending order. - """ - DESC + """ + Sort in descending order. + """ + DESC } """ Options for the `relations` argument on `@orderBy`. """ input OrderByRelation { - """ - TODO: description - """ - relation: String! - - """ - Restrict the allowed column names to a well-defined list. - This improves introspection capabilities and security. - Mutually exclusive with the `columnsEnum` argument. - """ - columns: [String!] - - """ - Use an existing enumeration type to restrict the allowed columns to a predefined list. - This allowes you to re-use the same enum for multiple fields. - Mutually exclusive with the `columns` argument. - """ - columnsEnum: String + """ + TODO: description + """ + relation: String! + + """ + Restrict the allowed column names to a well-defined list. + This improves introspection capabilities and security. + Mutually exclusive with the `columnsEnum` argument. + """ + columns: [String!] + + """ + Use an existing enumeration type to restrict the allowed columns to a predefined list. + This allowes you to re-use the same enum for multiple fields. + Mutually exclusive with the `columns` argument. + """ + columnsEnum: String } ``` diff --git a/docs/5/digging-deeper/ordering.md b/docs/5/digging-deeper/ordering.md index 8c6ca1c048..175141ef5a 100644 --- a/docs/5/digging-deeper/ordering.md +++ b/docs/5/digging-deeper/ordering.md @@ -57,10 +57,9 @@ You may pass more than one sorting option to add a secondary ordering. ```graphql { - posts(orderBy: [ - { column: POSTED_AT, order: ASC } - { column: TITLE, order: DESC } - ]) { + posts( + orderBy: [{ column: POSTED_AT, order: ASC }, { column: TITLE, order: DESC }] + ) { title } } @@ -96,14 +95,10 @@ You must specify which relations and which of their columns are allowed. ```graphql type Query { - users( - orderBy: _ @orderBy(relations: [ - { - relation: "tasks" - columns: ["difficulty"] - } - ]) - ): [User!]! @all + users( + orderBy: _ + @orderBy(relations: [{ relation: "tasks", columns: ["difficulty"] }]) + ): [User!]! @all } ``` @@ -111,18 +106,14 @@ Lighthouse will automatically generate the appropriate input types and enum valu ```graphql { - users(orderBy: [ - { - tasks: { aggregate: COUNT } - order: ASC - } - { - tasks: { aggregate: MAX, column: DIFFICULTY } - order: DESC - } - ]) { - id - } + users( + orderBy: [ + { tasks: { aggregate: COUNT }, order: ASC } + { tasks: { aggregate: MAX, column: DIFFICULTY }, order: DESC } + ] + ) { + id + } } ``` From 7feac4a07bf3baa7683dc7a7ec944ec0bfea8691 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 6 Dec 2021 15:06:11 +0000 Subject: [PATCH 14/20] Apply php-cs-fixer changes --- src/Console/IdeHelperCommand.php | 4 ++-- src/Schema/TypeRegistry.php | 18 +++++++++--------- src/Testing/TestResponseMixin.php | 2 +- src/Void/VoidServiceProvider.php | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Console/IdeHelperCommand.php b/src/Console/IdeHelperCommand.php index 3eb005002b..a0a679c96f 100644 --- a/src/Console/IdeHelperCommand.php +++ b/src/Console/IdeHelperCommand.php @@ -158,7 +158,7 @@ public function programmaticTypes(SchemaSourceProvider $schemaSourceProvider, AS $filePath = static::programmaticTypesPath(); - if (count($programmaticTypes) === 0 && file_exists($filePath)) { + if (0 === count($programmaticTypes) && file_exists($filePath)) { \Safe\unlink($filePath); return; @@ -174,7 +174,7 @@ function (Type $type): string { ) ); - \Safe\file_put_contents($filePath, self::GENERATED_NOTICE . $schema."\n"); + \Safe\file_put_contents($filePath, self::GENERATED_NOTICE . $schema . "\n"); $this->info("Wrote definitions for programmatically registered types to $filePath."); } diff --git a/src/Schema/TypeRegistry.php b/src/Schema/TypeRegistry.php index ae156e6590..1f70618124 100644 --- a/src/Schema/TypeRegistry.php +++ b/src/Schema/TypeRegistry.php @@ -329,9 +329,9 @@ protected function resolveObjectType(ObjectTypeDefinitionNode $objectDefinition) 'description' => $objectDefinition->description->value ?? null, 'fields' => $this->makeFieldsLoader($objectDefinition), 'interfaces' - /** - * @return list<\GraphQL\Type\Definition\Type> - */ => function () use ($objectDefinition): array { +/** + * @return list<\GraphQL\Type\Definition\Type> + */ => function () use ($objectDefinition): array { $interfaces = []; // Might be a NodeList, so we can not use array_map() @@ -381,9 +381,9 @@ protected function resolveInputObjectType(InputObjectTypeDefinitionNode $inputDe 'name' => $inputDefinition->name->value, 'description' => $inputDefinition->description->value ?? null, 'fields' - /** - * @return array> - */ => function () use ($inputDefinition): array { +/** + * @return array> + */ => function () use ($inputDefinition): array { return $this->argumentFactory->toTypeMap($inputDefinition->fields); }, 'astNode' => $inputDefinition, @@ -523,9 +523,9 @@ protected function resolveUnionType(UnionTypeDefinitionNode $unionDefinition): U 'name' => $nodeName, 'description' => $unionDefinition->description->value ?? null, 'types' - /** - * @return list<\GraphQL\Type\Definition\Type> - */ => function () use ($unionDefinition): array { +/** + * @return list<\GraphQL\Type\Definition\Type> + */ => function () use ($unionDefinition): array { $types = []; foreach ($unionDefinition->types as $type) { diff --git a/src/Testing/TestResponseMixin.php b/src/Testing/TestResponseMixin.php index b065da0685..29b3ac1a6f 100644 --- a/src/Testing/TestResponseMixin.php +++ b/src/Testing/TestResponseMixin.php @@ -78,7 +78,7 @@ public function assertGraphQLErrorFree(): Closure $errors = $this->json('errors'); Assert::assertNull( $errors, - 'Expected the GraphQL response to contain no errors, got: '.\Safe\json_encode($errors) + 'Expected the GraphQL response to contain no errors, got: ' . \Safe\json_encode($errors) ); return $this; diff --git a/src/Void/VoidServiceProvider.php b/src/Void/VoidServiceProvider.php index cfcbc371ea..09174b01bc 100644 --- a/src/Void/VoidServiceProvider.php +++ b/src/Void/VoidServiceProvider.php @@ -33,7 +33,7 @@ enum Unit { {$unit} } GRAPHQL -) + ) ); } ); From b3df20aecf1558e01c648752ff342decde02c4ab Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 6 Dec 2021 16:19:16 +0100 Subject: [PATCH 15/20] format --- src/Schema/TypeRegistry.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Schema/TypeRegistry.php b/src/Schema/TypeRegistry.php index 1f70618124..12c8a0166c 100644 --- a/src/Schema/TypeRegistry.php +++ b/src/Schema/TypeRegistry.php @@ -332,15 +332,15 @@ protected function resolveObjectType(ObjectTypeDefinitionNode $objectDefinition) /** * @return list<\GraphQL\Type\Definition\Type> */ => function () use ($objectDefinition): array { - $interfaces = []; + $interfaces = []; - // Might be a NodeList, so we can not use array_map() - foreach ($objectDefinition->interfaces as $interface) { - $interfaces[] = $this->get($interface->name->value); - } + // Might be a NodeList, so we can not use array_map() + foreach ($objectDefinition->interfaces as $interface) { + $interfaces[] = $this->get($interface->name->value); + } - return $interfaces; - }, + return $interfaces; +}, 'astNode' => $objectDefinition, ]); } @@ -523,9 +523,10 @@ protected function resolveUnionType(UnionTypeDefinitionNode $unionDefinition): U 'name' => $nodeName, 'description' => $unionDefinition->description->value ?? null, 'types' -/** - * @return list<\GraphQL\Type\Definition\Type> - */ => function () use ($unionDefinition): array { + /** + * @return list<\GraphQL\Type\Definition\Type> + */ + => function () use ($unionDefinition): array { $types = []; foreach ($unionDefinition->types as $type) { From 09dfad2e2486465572017bb430aa0fa3d81ca76c Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 6 Dec 2021 15:20:24 +0000 Subject: [PATCH 16/20] Apply php-cs-fixer changes --- src/Schema/TypeRegistry.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Schema/TypeRegistry.php b/src/Schema/TypeRegistry.php index 12c8a0166c..9b2d254e17 100644 --- a/src/Schema/TypeRegistry.php +++ b/src/Schema/TypeRegistry.php @@ -384,8 +384,8 @@ protected function resolveInputObjectType(InputObjectTypeDefinitionNode $inputDe /** * @return array> */ => function () use ($inputDefinition): array { - return $this->argumentFactory->toTypeMap($inputDefinition->fields); - }, + return $this->argumentFactory->toTypeMap($inputDefinition->fields); +}, 'astNode' => $inputDefinition, ]); } @@ -525,8 +525,7 @@ protected function resolveUnionType(UnionTypeDefinitionNode $unionDefinition): U 'types' /** * @return list<\GraphQL\Type\Definition\Type> - */ - => function () use ($unionDefinition): array { + */ => function () use ($unionDefinition): array { $types = []; foreach ($unionDefinition->types as $type) { From c4626f5cd9c1c9654365ae396a72c34edae95f16 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Tue, 14 Feb 2023 15:57:28 +0100 Subject: [PATCH 17/20] clarify test --- tests/Unit/Void/VoidDirectiveTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Void/VoidDirectiveTest.php b/tests/Unit/Void/VoidDirectiveTest.php index 422e4098d5..e91d56b0a0 100644 --- a/tests/Unit/Void/VoidDirectiveTest.php +++ b/tests/Unit/Void/VoidDirectiveTest.php @@ -5,13 +5,13 @@ use Nuwave\Lighthouse\Void\VoidServiceProvider; use Tests\TestCase; -class VoidDirectiveTest extends TestCase +final class VoidDirectiveTest extends TestCase { public function testVoid(): void { $this->schema = /** @lang GraphQL */ ' type Query { - foo: Int @void + foo: _ @void } '; From 89cc1ba09564fb91b50c36ce8bfd149a3f88aa5e Mon Sep 17 00:00:00 2001 From: spawnia Date: Tue, 14 Feb 2023 15:32:47 +0000 Subject: [PATCH 18/20] Apply php-cs-fixer changes --- src/Void/VoidDirective.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Void/VoidDirective.php b/src/Void/VoidDirective.php index f9d5f65487..21a8412be4 100644 --- a/src/Void/VoidDirective.php +++ b/src/Void/VoidDirective.php @@ -2,7 +2,6 @@ namespace Nuwave\Lighthouse\Void; -use Closure; use GraphQL\Language\AST\FieldDefinitionNode; use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\Parser; @@ -32,7 +31,7 @@ public function manipulateFieldDefinition(DocumentAST &$documentAST, FieldDefini $fieldDefinition->type = Parser::typeReference(/** @lang GraphQL */ 'Unit!'); } - public function handleField(FieldValue $fieldValue, Closure $next): FieldValue + public function handleField(FieldValue $fieldValue, \Closure $next): FieldValue { $fieldValue->resultHandler(static function (): string { return VoidServiceProvider::UNIT; From 8d0f33a111e438987bce41d81b6a7fb6352288c8 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 22 Feb 2023 15:16:42 +0100 Subject: [PATCH 19/20] fix middleware --- src/Void/VoidDirective.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Void/VoidDirective.php b/src/Void/VoidDirective.php index 21a8412be4..6d58911804 100644 --- a/src/Void/VoidDirective.php +++ b/src/Void/VoidDirective.php @@ -31,12 +31,9 @@ public function manipulateFieldDefinition(DocumentAST &$documentAST, FieldDefini $fieldDefinition->type = Parser::typeReference(/** @lang GraphQL */ 'Unit!'); } - public function handleField(FieldValue $fieldValue, \Closure $next): FieldValue + public function handleField(FieldValue $fieldValue): void { - $fieldValue->resultHandler(static function (): string { - return VoidServiceProvider::UNIT; - }); - - return $fieldValue; + // Just ignore whatever the original resolver is doing and always return a singular value + $fieldValue->resultHandler(static fn (): string => VoidServiceProvider::UNIT); } } From 3688ea9ed9d08acb9c8d06b028ac5c1160bda72c Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 22 Feb 2023 15:19:21 +0100 Subject: [PATCH 20/20] fix type variance --- src/Void/VoidDirective.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Void/VoidDirective.php b/src/Void/VoidDirective.php index 6d58911804..c3491b083e 100644 --- a/src/Void/VoidDirective.php +++ b/src/Void/VoidDirective.php @@ -3,6 +3,7 @@ namespace Nuwave\Lighthouse\Void; use GraphQL\Language\AST\FieldDefinitionNode; +use GraphQL\Language\AST\InterfaceTypeDefinitionNode; use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\Parser; use Nuwave\Lighthouse\Schema\AST\DocumentAST; @@ -26,7 +27,7 @@ public static function definition(): string GRAPHQL; } - public function manipulateFieldDefinition(DocumentAST &$documentAST, FieldDefinitionNode &$fieldDefinition, ObjectTypeDefinitionNode &$parentType) + public function manipulateFieldDefinition(DocumentAST &$documentAST, FieldDefinitionNode &$fieldDefinition, ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode &$parentType) { $fieldDefinition->type = Parser::typeReference(/** @lang GraphQL */ 'Unit!'); }