Skip to content

Commit

Permalink
Report errors thrown in async mutations
Browse files Browse the repository at this point in the history
  • Loading branch information
spawnia authored Jun 11, 2024
1 parent 426eb56 commit 7f9a6d8
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 19 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

## v6.37.0

### Changed

- Also report instances of `GraphQL\Error\Error` without previous exceptions in `ReportingErrorHandler` https://github.com/nuwave/lighthouse/pull/2567
- Report all errors thrown during the execution of async mutations regardless of client-safety https://github.com/nuwave/lighthouse/pull/2567

## v6.36.3

### Fixed
Expand Down
3 changes: 2 additions & 1 deletion docs/6/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ and the value `true` is returned - thus the fields return type must be `Boolean!
Once a [queue worker](https://laravel.com/docs/queues#running-the-queue-worker) picks up the job,
it will actually execute the underlying field resolver.
The result is not checked for errors, ensure your GraphQL error handling reports relevant exceptions.
Errors that occur during execution are reported through the Laravel exception handler.
The handlers in the `config/lighthouse.php` option `error_handlers` are not called.
"""
directive @async(
"""
Expand Down
2 changes: 1 addition & 1 deletion docs/6/custom-directives/field-directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ directive can be used to manipulate the schema AST.
This directive type is implemented as an abstract class rather than a pure interface and allows
you to define complex validation rules for a field with ease.

[Read more about it in the Validation section](../security/validation.md#validate-fields).
[Read more about it in the Validation section](../security/validation.md#validator-for-fields).

## ComplexityResolverDirective

Expand Down
18 changes: 10 additions & 8 deletions docs/6/custom-directives/getting-started.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Implementing Your Own Directives

As you grow your GraphQL schema, you may find the need for more specialized functionality.
Learn how you can abstract logic in a composable and reusable manner by using custom directives.

Directives are implemented as PHP classes, each directive available
in the schema corresponds to a single class.
You can abstract logic in a composable and reusable manner by using custom directives.

## Naming Convention

Directives are implemented as PHP classes.
Each directive available in the schema corresponds to a single class.

Directive names themselves are typically defined in **camelCase**.
The class name of a directive must follow the following pattern:

Expand Down Expand Up @@ -51,12 +51,11 @@ GRAPHQL;
## Directive Interfaces

At this point, the directive does not do anything.
Depending on what your directive should do, you can pick one or more of the provided
directive interfaces to add functionality. They serve as the point of contact to Lighthouse.
Depending on what your directive should do, you can pick one or more of the provided directive interfaces to add functionality.
They serve as the point of contact to Lighthouse.

In this case, our directive needs to run after the actual resolver.
Just like [Laravel Middleware](https://laravel.com/docs/middleware),
we can wrap around it by using the `FieldMiddleware` directive.
Just like [Laravel Middleware](https://laravel.com/docs/middleware), we can wrap around it by using the `FieldMiddleware` directive.

```php
namespace App\GraphQL\Directives;
Expand Down Expand Up @@ -87,6 +86,9 @@ final class UpperCaseDirective extends BaseDirective implements FieldMiddleware
}
```

Given there are a lot of use cases for custom directives, the documentation does not provide examples for most interfaces.
It is advised to look at the Lighthouse source code to find directives that implement certain interfaces to learn more.

## Register Directives

Now that we defined and implemented the directive, how can Lighthouse find it?
Expand Down
3 changes: 2 additions & 1 deletion docs/master/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ and the value `true` is returned - thus the fields return type must be `Boolean!
Once a [queue worker](https://laravel.com/docs/queues#running-the-queue-worker) picks up the job,
it will actually execute the underlying field resolver.
The result is not checked for errors, ensure your GraphQL error handling reports relevant exceptions.
Errors that occur during execution are reported through the Laravel exception handler.
The handlers in the `config/lighthouse.php` option `error_handlers` are not called.
"""
directive @async(
"""
Expand Down
3 changes: 2 additions & 1 deletion src/Async/AsyncDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public static function definition(): string
Once a [queue worker](https://laravel.com/docs/queues#running-the-queue-worker) picks up the job,
it will actually execute the underlying field resolver.
The result is not checked for errors, ensure your GraphQL error handling reports relevant exceptions.
Errors that occur during execution are reported through the Laravel exception handler.
The handlers in the `config/lighthouse.php` option `error_handlers` are not called.
"""
directive @async(
"""
Expand Down
11 changes: 9 additions & 2 deletions src/Async/AsyncMutation.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\OperationDefinitionNode;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
Expand All @@ -28,15 +29,21 @@ public function __construct(
public array $variableValues,
) {}

public function handle(GraphQL $graphQL, SerializesContext $serializesContext): void
public function handle(GraphQL $graphQL, SerializesContext $serializesContext, ExceptionHandler $exceptionHandler): void
{
$graphQL->executeParsedQuery(
$result = $graphQL->executeParsedQueryRaw(
$this->query(),
$serializesContext->unserialize($this->serializedContext),
$this->variableValues,
AsyncRoot::instance(),
$this->operation->name?->value,
);

foreach ($result->errors as $error) {
$exceptionHandler->report(
$error->getPrevious() ?? $error,
);
}
}

protected function query(): DocumentNode
Expand Down
7 changes: 3 additions & 4 deletions src/Execution/ReportingErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ public function __invoke(?Error $error, \Closure $next): ?array
return $next($error);
}

$previous = $error->getPrevious();
if ($previous !== null) {
$this->exceptionHandler->report($previous);
}
$this->exceptionHandler->report(
$error->getPrevious() ?? $error,
);

return $next($error);
}
Expand Down
19 changes: 18 additions & 1 deletion src/GraphQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ public function executeParsedQuery(
mixed $root = null,
?string $operationName = null,
): array {
$result = $this->executeParsedQueryRaw($query, $context, $variables, $root, $operationName);

return $this->toSerializableArray($result);
}

/**
* Execute a GraphQL query on the Lighthouse schema and return the raw result.
*
* @param array<string, mixed>|null $variables
*/
public function executeParsedQueryRaw(
DocumentNode $query,
GraphQLContext $context,
?array $variables = [],
mixed $root = null,
?string $operationName = null,
): ExecutionResult {
// Building the executable schema might take a while to do,
// so we do it before we fire the StartExecution event.
// This allows tracking the time for batched queries independently.
Expand Down Expand Up @@ -148,7 +165,7 @@ public function executeParsedQuery(

$this->cleanUpAfterExecution();

return $this->toSerializableArray($result);
return $result;
}

/**
Expand Down

0 comments on commit 7f9a6d8

Please sign in to comment.