Add Debug::RETHROW_UNSAFE_EXCEPTIONS flag

This allows for a more granular default for which Exceptions are thrown, taking
the settings made through the ClientAware interface into account.

- Add docs
- Add a test case
- Differentiate clearly from other test
This commit is contained in:
spawnia 2018-09-03 21:18:04 +02:00
parent 503ac4619a
commit 326e0b4719
5 changed files with 73 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

@ -235,9 +235,7 @@ class FormattedError
$debug = (int) $debug; $debug = (int) $debug;
$isInternal = ! $e instanceof ClientAware || ! $e->isClientSafe(); if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
if (($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) && $isInternal) {
if (! $e instanceof Error) { if (! $e instanceof Error) {
throw $e; throw $e;
} }
@ -247,7 +245,15 @@ class FormattedError
} }
} }
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isInternal) { $isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
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

@ -1,11 +1,9 @@
<?php <?php
namespace GraphQL\Tests\Server; namespace GraphQL\Tests\Server;
use GraphQL\Deferred;
use GraphQL\Error\Debug; use GraphQL\Error\Debug;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Error\UserError;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
@ -64,20 +62,20 @@ 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' => []
] ]
] ]
@ -86,6 +84,18 @@ class QueryExecutionTest extends ServerTestCase
$result = $this->executeQuery($query)->toArray(); $result = $this->executeQuery($query)->toArray();
$this->assertArraySubset($expected, $result); $this->assertArraySubset($expected, $result);
} }
public function testRethrowUnsafeExceptions() : void
{
$this->config->setDebug(Debug::RETHROW_UNSAFE_EXCEPTIONS);
$this->expectException(UnsafeException::class);
$this->executeQuery('
{
fieldWithUnsafeException
}
')->toArray();
}
public function testPassesRootValueAndContext() : void public function testPassesRootValueAndContext() : void
{ {
@ -240,7 +250,7 @@ class QueryExecutionTest extends ServerTestCase
'query' => '{invalid}' 'query' => '{invalid}'
], ],
[ [
'query' => '{f1,fieldWithException}' 'query' => '{f1,fieldWithSafeException}'
] ]
]; ];
@ -405,7 +415,7 @@ class QueryExecutionTest extends ServerTestCase
'query' => '{invalid}' 'query' => '{invalid}'
], ],
[ [
'query' => '{f1,fieldWithException}' 'query' => '{f1,fieldWithSafeException}'
], ],
[ [
'query' => ' 'query' => '
@ -427,7 +437,7 @@ class QueryExecutionTest extends ServerTestCase
[ [
'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']
@ -581,7 +591,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 = [
@ -620,7 +630,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

@ -3,6 +3,7 @@ namespace GraphQL\Tests\Server;
use GraphQL\Deferred; use GraphQL\Deferred;
use GraphQL\Error\ClientAware;
use GraphQL\Error\UserError; use GraphQL\Error\UserError;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
@ -34,10 +35,16 @@ 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 UnsafeException('This exception should not be shown to the user');
} }
], ],
'testContextAndRootValue' => [ 'testContextAndRootValue' => [
@ -92,3 +99,30 @@ abstract class ServerTestCase extends TestCase
return $schema; return $schema;
} }
} }
class UnsafeException 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';
}
}