-
-
Notifications
You must be signed in to change notification settings - Fork 438
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make reporting of client-safe errors configurable (#2647)
- Loading branch information
1 parent
2f0cd99
commit cc4b1d1
Showing
7 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Nuwave\Lighthouse\Execution; | ||
|
||
use GraphQL\Error\Error; | ||
use Illuminate\Contracts\Debug\ExceptionHandler; | ||
|
||
class AlwaysReportingErrorHandler implements ErrorHandler | ||
{ | ||
public function __construct( | ||
protected ExceptionHandler $exceptionHandler, | ||
) {} | ||
|
||
public function __invoke(?Error $error, \Closure $next): ?array | ||
{ | ||
if ($error === null) { | ||
return $next(null); | ||
} | ||
|
||
$this->exceptionHandler->report( | ||
$error->getPrevious() ?? $error, | ||
); | ||
|
||
return $next($error); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Tests; | ||
|
||
use Illuminate\Contracts\Debug\ExceptionHandler; | ||
use PHPUnit\Framework\Assert; | ||
use Symfony\Component\HttpFoundation\Response; | ||
|
||
final class FakeExceptionHandler implements ExceptionHandler | ||
{ | ||
/** @var array<\Throwable> */ | ||
private array $reported = []; | ||
|
||
public function report(\Throwable $e): void | ||
{ | ||
$this->reported[] = $e; | ||
} | ||
|
||
public function shouldReport(\Throwable $e): bool | ||
{ | ||
return true; | ||
} | ||
|
||
public function assertNothingReported(): void | ||
{ | ||
Assert::assertEmpty($this->reported); | ||
} | ||
|
||
public function assertReported(\Throwable $e): void | ||
{ | ||
Assert::assertContainsEquals($e, $this->reported); | ||
} | ||
|
||
public function render($request, \Throwable $e): Response | ||
{ | ||
throw $e; | ||
} | ||
|
||
public function renderForConsole($output, \Throwable $e) {} | ||
} |
80 changes: 80 additions & 0 deletions
80
tests/Integration/Execution/AlwaysReportingErrorHandlerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Tests\Integration\Execution; | ||
|
||
use GraphQL\Error\Error; | ||
use Nuwave\Lighthouse\Execution\AlwaysReportingErrorHandler; | ||
use Tests\FakeExceptionHandler; | ||
use Tests\TestCase; | ||
use Tests\Utils\Exceptions\ClientAwareException; | ||
|
||
final class AlwaysReportingErrorHandlerTest extends TestCase | ||
{ | ||
private FakeExceptionHandler $handler; | ||
|
||
/** @before */ | ||
public function fakeExceptionHandling(): void | ||
{ | ||
$this->afterApplicationCreated(function (): void { | ||
$this->withoutExceptionHandling(); | ||
$this->handler = new FakeExceptionHandler(); | ||
}); | ||
$this->beforeApplicationDestroyed(function (): void { | ||
unset($this->handler); | ||
}); | ||
} | ||
|
||
public function testHandlingWhenThereIsNoError(): void | ||
{ | ||
$next = fn (?Error $error): array => match ($error) { | ||
null => ['error' => 'No error to report'], | ||
default => throw new \LogicException('Unexpected error: ' . $error::class), | ||
}; | ||
|
||
$result = (new AlwaysReportingErrorHandler($this->handler))(null, $next); | ||
|
||
$this->assertSame(['error' => 'No error to report'], $result); | ||
$this->handler->assertNothingReported(); | ||
} | ||
|
||
/** @return iterable<array{\Exception}> */ | ||
public static function nonClientSafeErrors(): iterable | ||
{ | ||
yield 'Previous error is not client aware' => [new \Exception('Not client aware')]; | ||
yield 'Previous error is not client safe' => [ClientAwareException::notClientSafe()]; | ||
} | ||
|
||
/** @dataProvider nonClientSafeErrors */ | ||
public function testNonClientSafeErrors(\Exception $previousError): void | ||
{ | ||
$error = new Error(previous: $previousError); | ||
$next = fn (Error $error): array => \compact('error'); | ||
|
||
$result = (new AlwaysReportingErrorHandler($this->handler))($error, $next); | ||
|
||
$this->assertSame(\compact('error'), $result); | ||
$this->handler->assertReported($previousError); | ||
} | ||
|
||
/** @return iterable<array{\Exception|null}> */ | ||
public static function clientSafeErrors(): iterable | ||
{ | ||
yield 'No previous error' => [null]; | ||
yield 'Previous error is client safe' => [ClientAwareException::clientSafe()]; | ||
} | ||
|
||
/** @dataProvider clientSafeErrors */ | ||
public function testClientSafeErrors(?\Exception $previousError): void | ||
{ | ||
$error = new Error(previous: $previousError); | ||
$next = fn (Error $error): array => \compact('error'); | ||
|
||
$result = (new AlwaysReportingErrorHandler($this->handler))($error, $next); | ||
|
||
$this->assertSame(\compact('error'), $result); | ||
$this->handler->assertReported(match ($previousError) { | ||
null => $error, | ||
default => $previousError, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Tests\Integration\Execution; | ||
|
||
use GraphQL\Error\Error; | ||
use Nuwave\Lighthouse\Execution\ReportingErrorHandler; | ||
use Tests\FakeExceptionHandler; | ||
use Tests\TestCase; | ||
use Tests\Utils\Exceptions\ClientAwareException; | ||
|
||
final class ReportingErrorHandlerTest extends TestCase | ||
{ | ||
private FakeExceptionHandler $handler; | ||
|
||
/** @before */ | ||
public function fakeExceptionHandling(): void | ||
{ | ||
$this->afterApplicationCreated(function (): void { | ||
$this->withoutExceptionHandling(); | ||
$this->handler = new FakeExceptionHandler(); | ||
}); | ||
$this->beforeApplicationDestroyed(function (): void { | ||
unset($this->handler); | ||
}); | ||
} | ||
|
||
public function testHandlingWhenThereIsNoError(): void | ||
{ | ||
$next = fn (?Error $error): array => match ($error) { | ||
null => ['error' => 'No error to report'], | ||
default => throw new \LogicException('Unexpected error: ' . $error::class), | ||
}; | ||
|
||
$result = (new ReportingErrorHandler($this->handler))(null, $next); | ||
|
||
$this->assertSame(['error' => 'No error to report'], $result); | ||
$this->handler->assertNothingReported(); | ||
} | ||
|
||
/** @return iterable<array{\Exception}> */ | ||
public static function nonClientSafe(): iterable | ||
{ | ||
yield 'Previous error is not client aware' => [new \Exception('Not client aware')]; | ||
yield 'Previous error is not client safe' => [ClientAwareException::notClientSafe()]; | ||
} | ||
|
||
/** @dataProvider nonClientSafe */ | ||
public function testNonClientSafeErrors(\Exception $previousError): void | ||
{ | ||
$error = new Error(previous: $previousError); | ||
$next = fn (Error $error): array => \compact('error'); | ||
|
||
$result = (new ReportingErrorHandler($this->handler))($error, $next); | ||
|
||
$this->assertSame(\compact('error'), $result); | ||
$this->handler->assertReported($previousError); | ||
} | ||
|
||
/** @return iterable<array{\Exception|null}> */ | ||
public static function clientSafeErrors(): iterable | ||
{ | ||
yield 'No previous error' => [null]; | ||
yield 'Previous error is client safe' => [ClientAwareException::clientSafe()]; | ||
} | ||
|
||
/** @dataProvider clientSafeErrors */ | ||
public function testClientSafeErrors(?\Exception $previousError): void | ||
{ | ||
$error = new Error(previous: $previousError); | ||
$next = fn (Error $error): array => \compact('error'); | ||
|
||
$result = (new ReportingErrorHandler($this->handler))($error, $next); | ||
|
||
$this->assertSame(\compact('error'), $result); | ||
$this->handler->assertNothingReported(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tests\Utils\Exceptions; | ||
|
||
use GraphQL\Error\ClientAware; | ||
|
||
final class ClientAwareException extends \Exception implements ClientAware | ||
{ | ||
private function __construct( | ||
private bool $clientSafe, | ||
) { | ||
parent::__construct('Client Aware Error'); | ||
} | ||
|
||
public static function clientSafe(): self | ||
{ | ||
return new self(true); | ||
} | ||
|
||
public static function notClientSafe(): self | ||
{ | ||
return new self(false); | ||
} | ||
|
||
public function isClientSafe(): bool | ||
{ | ||
return $this->clientSafe; | ||
} | ||
} |