Merge pull request #337 from spawnia/patch-1

Add Debug::RETHROW_UNSAFE_EXCEPTIONS flag
This commit is contained in:
Vladimir Razuvaev 2018-09-06 12:09:05 +07:00 committed by GitHub
commit 6bdead3fe3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 19 deletions

View File

@ -84,7 +84,7 @@ To change default **"Internal server error"** message to something else, use:
GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error"); GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error");
``` ```
#Debugging tools # Debugging tools
During development or debugging use `$result->toArray(true)` to add **debugMessage** key to During development or debugging use `$result->toArray(true)` to add **debugMessage** key to
each formatted error entry. If you also want to add exception trace - pass flags instead: each formatted error entry. If you also want to add exception trace - pass flags instead:
@ -116,7 +116,7 @@ This will make each error entry to look like this:
]; ];
``` ```
If you prefer first resolver exception to be re-thrown, use following flags: If you prefer the first resolver exception to be re-thrown, use following flags:
```php ```php
<?php <?php
use GraphQL\GraphQL; use GraphQL\GraphQL;
@ -127,6 +127,9 @@ $debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::RETHROW_INTERNAL_EXCEPTIONS;
$result = GraphQL::executeQuery(/*args*/)->toArray($debug); $result = GraphQL::executeQuery(/*args*/)->toArray($debug);
``` ```
If you only want to re-throw Exceptions that are not marked as safe through the `ClientAware` interface, use
the flag `Debug::RETHROW_UNSAFE_EXCEPTIONS`.
# Custom Error Handling and Formatting # Custom Error Handling and Formatting
It is possible to define custom **formatter** and **handler** for result errors. It is possible to define custom **formatter** and **handler** for result errors.

View File

@ -12,4 +12,5 @@ class Debug
const INCLUDE_DEBUG_MESSAGE = 1; const INCLUDE_DEBUG_MESSAGE = 1;
const INCLUDE_TRACE = 2; const INCLUDE_TRACE = 2;
const RETHROW_INTERNAL_EXCEPTIONS = 4; const RETHROW_INTERNAL_EXCEPTIONS = 4;
const RETHROW_UNSAFE_EXCEPTIONS = 8;
} }

View File

@ -245,9 +245,15 @@ class FormattedError
} }
} }
$isInternal = ! $e instanceof ClientAware || ! $e->isClientSafe(); $isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isInternal) { if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) && $isUnsafe) {
if ($e->getPrevious()) {
throw $e->getPrevious();
}
}
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isUnsafe) {
// Displaying debugMessage as a first entry: // Displaying debugMessage as a first entry:
$formattedError = ['debugMessage' => $e->getMessage()] + $formattedError; $formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
} }

View File

@ -81,21 +81,21 @@ class QueryExecutionTest extends ServerTestCase
$query = ' $query = '
{ {
fieldWithException fieldWithSafeException
f1 f1
} }
'; ';
$expected = [ $expected = [
'data' => [ 'data' => [
'fieldWithException' => null, 'fieldWithSafeException' => null,
'f1' => 'f1', 'f1' => 'f1',
], ],
'errors' => [ 'errors' => [
[ [
'message' => 'This is the exception we want', 'message' => 'This is the exception we want',
'path' => ['fieldWithException'], 'path' => ['fieldWithSafeException'],
'trace' => [], 'trace' => [],
], ],
], ],
]; ];
@ -104,6 +104,18 @@ class QueryExecutionTest extends ServerTestCase
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
} }
public function testRethrowUnsafeExceptions() : void
{
$this->config->setDebug(Debug::RETHROW_UNSAFE_EXCEPTIONS);
$this->expectException(Unsafe::class);
$this->executeQuery('
{
fieldWithUnsafeException
}
')->toArray();
}
public function testPassesRootValueAndContext() : void public function testPassesRootValueAndContext() : void
{ {
$rootValue = 'myRootValue'; $rootValue = 'myRootValue';
@ -268,7 +280,7 @@ class QueryExecutionTest extends ServerTestCase
{ {
$batch = [ $batch = [
['query' => '{invalid}'], ['query' => '{invalid}'],
['query' => '{f1,fieldWithException}'], ['query' => '{f1,fieldWithSafeException}'],
]; ];
$result = $this->executeBatchedQuery($batch); $result = $this->executeBatchedQuery($batch);
@ -453,7 +465,7 @@ class QueryExecutionTest extends ServerTestCase
$batch = [ $batch = [
['query' => '{invalid}'], ['query' => '{invalid}'],
['query' => '{f1,fieldWithException}'], ['query' => '{f1,fieldWithSafeException}'],
[ [
'query' => ' 'query' => '
query ($a: String!, $b: String!) { query ($a: String!, $b: String!) {
@ -472,9 +484,9 @@ class QueryExecutionTest extends ServerTestCase
'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']], 'errors' => [['message' => 'Cannot query field "invalid" on type "Query".']],
], ],
[ [
'data' => [ 'data' => [
'f1' => 'f1', 'f1' => 'f1',
'fieldWithException' => null, 'fieldWithSafeException' => null,
], ],
'errors' => [ 'errors' => [
['message' => 'This is the exception we want'], ['message' => 'This is the exception we want'],
@ -618,7 +630,7 @@ class QueryExecutionTest extends ServerTestCase
return ['test' => 'formatted']; return ['test' => 'formatted'];
}); });
$result = $this->executeQuery('{fieldWithException}'); $result = $this->executeQuery('{fieldWithSafeException}');
$this->assertFalse($called); $this->assertFalse($called);
$formatted = $result->toArray(); $formatted = $result->toArray();
$expected = [ $expected = [
@ -658,7 +670,7 @@ class QueryExecutionTest extends ServerTestCase
]; ];
}); });
$result = $this->executeQuery('{fieldWithException,test: fieldWithException}'); $result = $this->executeQuery('{fieldWithSafeException,test: fieldWithSafeException}');
$this->assertFalse($called); $this->assertFalse($called);
$formatted = $result->toArray(); $formatted = $result->toArray();

View File

@ -41,12 +41,18 @@ abstract class ServerTestCase extends TestCase
return $info->fieldName; return $info->fieldName;
}, },
], ],
'fieldWithException' => [ 'fieldWithSafeException' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($root, $args, $context, $info) { 'resolve' => function () {
throw new UserError('This is the exception we want'); throw new UserError('This is the exception we want');
}, },
], ],
'fieldWithUnsafeException' => [
'type' => Type::string(),
'resolve' => function () {
throw new Unsafe('This exception should not be shown to the user');
},
],
'testContextAndRootValue' => [ 'testContextAndRootValue' => [
'type' => Type::string(), 'type' => Type::string(),
'resolve' => function ($root, $args, $context, $info) { 'resolve' => function ($root, $args, $context, $info) {

34
tests/Server/Unsafe.php Normal file
View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace GraphQL\Tests\Server;
use GraphQL\Error\ClientAware;
class Unsafe extends \Exception implements ClientAware
{
/**
* Returns true when exception message is safe to be displayed to a client.
*
* @api
* @return bool
*/
public function isClientSafe()
{
return false;
}
/**
* Returns string describing a category of the error.
*
* Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.
*
* @api
* @return string
*/
public function getCategory()
{
return 'unsafe';
}
}