diff --git a/CHANGELOG.md b/CHANGELOG.md index 8621ab7a4f..97ea03d34a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ You can find and compare releases at the [GitHub release page](https://github.co - Support Laravel 10 https://github.com/nuwave/lighthouse/pull/2287 - Add `GraphQLContext:: setUser(?Authenticatable $user): void` - Add directive `@whereKey` to filter Models by their primary key https://github.com/nuwave/lighthouse/pull/2289 +- Add directive `@void` to unify the definition of fields with no return value https://github.com/nuwave/lighthouse/pull/1992 - Allow directive `@where` on fields https://github.com/nuwave/lighthouse/pull/2306 ### Removed diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index 56018aa725..1e9a2adf70 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -3557,6 +3557,51 @@ 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. + +https://en.wikipedia.org/wiki/Unit_type +""" +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: _ @void +} +``` + +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 ```graphql diff --git a/src/Void/VoidDirective.php b/src/Void/VoidDirective.php new file mode 100644 index 0000000000..c3491b083e --- /dev/null +++ b/src/Void/VoidDirective.php @@ -0,0 +1,40 @@ +type = Parser::typeReference(/** @lang GraphQL */ 'Unit!'); + } + + public function handleField(FieldValue $fieldValue): void + { + // Just ignore whatever the original resolver is doing and always return a singular value + $fieldValue->resultHandler(static fn (): string => VoidServiceProvider::UNIT); + } +} diff --git a/src/Void/VoidServiceProvider.php b/src/Void/VoidServiceProvider.php new file mode 100644 index 0000000000..09174b01bc --- /dev/null +++ b/src/Void/VoidServiceProvider.php @@ -0,0 +1,48 @@ +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 b091d4276c..e00dc3ce97 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -26,6 +26,7 @@ use Nuwave\Lighthouse\Testing\TestingServiceProvider; 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; @@ -89,6 +90,7 @@ protected function getPackageProviders($app): array SoftDeletesServiceProvider::class, TestingServiceProvider::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..e91d56b0a0 --- /dev/null +++ b/tests/Unit/Void/VoidDirectiveTest.php @@ -0,0 +1,28 @@ +schema = /** @lang GraphQL */ ' + type Query { + foo: _ @void + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + foo + } + ')->assertJson([ + 'data' => [ + 'foo' => VoidServiceProvider::UNIT, + ], + ]); + } +}