diff --git a/UPGRADE.md b/UPGRADE.md index e4985c4..3d774ba 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,15 +1,43 @@ # Upgrade ## Upgrade v0.9.x > v0.10.x -### Deprecated: GraphQL\Utils moved to GraphQL\Utils\Utils -Old class still exists, but triggers deprecation warning. +#### Breaking: default error formatting +By default exceptions thrown in resolvers will be reported with generic message `"Internal server error"`. +Only exceptions implementing interface `GraphQL\Error\ClientAware` and claiming themselves as `safe` will +be reported with full error message. + +This is done to avoid information leak in production when unhandled exceptions occur in resolvers +(e.g. database connection errors, file access errors, etc). + +During development or debugging use `$executionResult->toArray(true)`. It will add `debugMessage` key to +each error entry in result. If you also want to add `trace` for each error - pass flags instead: + +``` +use GraphQL\Error\FormattedError; +$debug = FormattedError::INCLUDE_DEBUG_MESSAGE | FormattedError::INCLUDE_TRACE; +$result = GraphQL::executeAndReturnResult()->toArray($debug); +``` + +To change default `"Internal server error"` message to something else, use: +``` +GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error"); +``` + +**This change only affects default error reporting mechanism. If you set your own error formatter using +`ExecutionResult::setErrorFormatter()` you won't be affected by this change.** + +If you are not happy with this change just set [your own error +formatter](http://webonyx.github.io/graphql-php/error-handling/#custom-error-formatting). + +#### Deprecated: GraphQL\Utils moved to GraphQL\Utils\Utils +Old class still exists, but triggers deprecation warning when referenced. ## Upgrade v0.7.x > v0.8.x All of those changes apply to those who extends various parts of this library. If you only use the library and don't try to extend it - everything should work without breaks. -### Breaking: Custom directives handling +#### Breaking: Custom directives handling When passing custom directives to schema, default directives (like `@skip` and `@include`) are not added to schema automatically anymore. If you need them - add them explicitly with your other directives. @@ -30,15 +58,15 @@ $schema = new Schema([ ]); ``` -### Breaking: Schema protected property and methods visibility +#### Breaking: Schema protected property and methods visibility Most of the `protected` properties and methods of `GraphQL\Schema` were changed to `private`. Please use public interface instead. -### Breaking: Node kind constants +#### Breaking: Node kind constants Node kind constants were extracted from `GraphQL\Language\AST\Node` to separate class `GraphQL\Language\AST\NodeKind` -### Non-breaking: AST node classes renamed +#### Non-breaking: AST node classes renamed AST node classes were renamed to disambiguate with types. e.g.: ``` @@ -50,7 +78,7 @@ etc. Old names are still available via `class_alias` defined in `src/deprecated.php`. This file is included automatically when using composer autoloading. -### Deprecations +#### Deprecations There are several deprecations which still work, but trigger `E_USER_DEPRECATED` when used. For example `GraphQL\Executor\Executor::setDefaultResolveFn()` is renamed to `setDefaultResolver()` @@ -61,7 +89,7 @@ but still works with old name. There are a few new breaking changes in v0.7.0 that were added to the graphql-js reference implementation with the spec of April2016 -### 1. Context for resolver +#### 1. Context for resolver You can now pass a custom context to the `GraphQL::execute` function that is available in all resolvers as 3rd argument. This can for example be used to pass the current user etc. @@ -119,7 +147,7 @@ function resolveMyField($object, array $args, $context, ResolveInfo $info){ } ``` -### 2. Schema constructor signature +#### 2. Schema constructor signature The signature of the Schema constructor now accepts an associative config array instead of positional arguments: @@ -137,7 +165,7 @@ $schema = new Schema([ ]); ``` -### 3. Types can be directly passed to schema +#### 3. Types can be directly passed to schema There are edge cases when GraphQL cannot infer some types from your schema. One example is when you define a field of interface type and object types implementing diff --git a/docs/error-handling.md b/docs/error-handling.md index 998dbb1..d8d3dbe 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -113,6 +113,28 @@ exceptions thrown by resolvers. To access original exceptions use `$error->getPr But note that previous exception is only available for **Execution** errors and will be `null` for **Syntax** or **Validation** errors. +For example: + +```php +$result = GraphQL::executeAndReturnResult() + ->setErrorFormatter(function(GraphQL\Error\Error $err) { + $resolverException = $err->getPrevious(); + + if ($resolverException instanceof MyResolverException) { + $formattedError = [ + 'message' => $resolverException->getMessage(), + 'code' => $resolverException->getCode() + ]; + } else { + $formattedError = [ + 'message' => $err->getMessage() + ]; + } + return $formattedError; + }) + ->toArray(); +``` + # Schema Errors So far we only covered errors which occur during query execution process. But schema definition can also throw if there is an error in one of type definitions. diff --git a/src/Error/ClientAware.php b/src/Error/ClientAware.php new file mode 100644 index 0000000..26ab969 --- /dev/null +++ b/src/Error/ClientAware.php @@ -0,0 +1,17 @@ +source = $source; $this->positions = $positions; $this->path = $path; + + if ($previous instanceof ClientAware) { + $this->isClientSafe = $previous->isClientSafe(); + } else if ($previous === null) { + $this->isClientSafe = true; + } else { + $this->isClientSafe = false; + } + } + + /** + * @return bool + */ + public function isClientSafe() + { + return $this->isClientSafe; } /** @@ -190,6 +211,7 @@ class Error extends \Exception implements \JsonSerializable /** * Returns array representation of error suitable for serialization * + * @deprecated Use FormattedError::createFromException() instead * @return array */ public function toSerializableArray() diff --git a/src/Error/FormattedError.php b/src/Error/FormattedError.php index 6664c20..7e611b8 100644 --- a/src/Error/FormattedError.php +++ b/src/Error/FormattedError.php @@ -13,20 +13,67 @@ use GraphQL\Utils\Utils; */ class FormattedError { + const INCLUDE_DEBUG_MESSAGE = 1; + const INCLUDE_TRACE = 2; + + private static $internalErrorMessage = 'Internal server error'; + + public static function setInternalErrorMessage($msg) + { + self::$internalErrorMessage = $msg; + } + /** * @param \Throwable $e - * @param $debug + * @param bool|int $debug + * @param string $internalErrorMessage * * @return array */ - public static function createFromException($e, $debug = false) + public static function createFromException($e, $debug = false, $internalErrorMessage = null) { - if ($e instanceof Error) { - $result = $e->toSerializableArray(); - } else if ($e instanceof \ErrorException) { + Utils::invariant( + $e instanceof \Exception || $e instanceof \Throwable, + "Expected exception, got %s", + Utils::getVariableType($e) + ); + + $debug = (int) $debug; + $internalErrorMessage = $internalErrorMessage ?: self::$internalErrorMessage; + + if ($e instanceof ClientAware) { + if ($e->isClientSafe()) { + $result = [ + 'message' => $e->getMessage() + ]; + } else { + $result = [ + 'message' => $internalErrorMessage, + 'isInternalError' => true + ]; + } + } else { $result = [ - 'message' => $e->getMessage(), + 'message' => $internalErrorMessage, + 'isInternalError' => true ]; + } + if (($debug & self::INCLUDE_DEBUG_MESSAGE > 0) && $result['message'] === $internalErrorMessage) { + $result['debugMessage'] = $e->getMessage(); + } + + if ($e instanceof Error) { + $locations = Utils::map($e->getLocations(), function(SourceLocation $loc) { + return $loc->toSerializableArray(); + }); + + if (!empty($locations)) { + $result['locations'] = $locations; + } + if (!empty($e->path)) { + $result['path'] = $e->path; + } + } else if ($e instanceof \ErrorException) { if ($debug) { $result += [ 'file' => $e->getFile(), @@ -34,18 +81,16 @@ class FormattedError 'severity' => $e->getSeverity() ]; } - } else { - Utils::invariant( - $e instanceof \Exception || $e instanceof \Throwable, - "Expected exception, got %s", - Utils::getVariableType($e) - ); - $result = [ - 'message' => $e->getMessage() - ]; + } else if ($e instanceof \Error) { + if ($debug) { + $result += [ + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ]; + } } - if ($debug) { + if ($debug & self::INCLUDE_TRACE > 0) { $debugging = $e->getPrevious() ?: $e; $result['trace'] = static::toSafeTrace($debugging->getTrace()); } diff --git a/src/Error/UserError.php b/src/Error/UserError.php index 8276c94..bf283a5 100644 --- a/src/Error/UserError.php +++ b/src/Error/UserError.php @@ -4,10 +4,17 @@ namespace GraphQL\Error; /** * Class UserError * - * Error that can be safely displayed to client... + * Error caused by actions of GraphQL clients. Can be safely displayed to client... * * @package GraphQL\Error */ -class UserError extends InvariantViolation +class UserError extends \RuntimeException implements ClientAware { + /** + * @return bool + */ + public function isClientSafe() + { + return true; + } } diff --git a/src/Executor/ExecutionResult.php b/src/Executor/ExecutionResult.php index 2628a69..6666bbc 100644 --- a/src/Executor/ExecutionResult.php +++ b/src/Executor/ExecutionResult.php @@ -2,6 +2,7 @@ namespace GraphQL\Executor; use GraphQL\Error\Error; +use GraphQL\Error\FormattedError; class ExecutionResult implements \JsonSerializable { @@ -23,7 +24,7 @@ class ExecutionResult implements \JsonSerializable /** * @var callable */ - private $errorFormatter = ['GraphQL\Error\Error', 'formatError']; + private $errorFormatter; /** * @param array $data @@ -48,14 +49,26 @@ class ExecutionResult implements \JsonSerializable } /** + * @param bool|int $debug * @return array */ - public function toArray() + public function toArray($debug = false) { $result = []; if (!empty($this->errors)) { - $result['errors'] = array_map($this->errorFormatter, $this->errors); + if ($debug) { + $errorFormatter = function($e) use ($debug) { + return FormattedError::createFromException($e, $debug); + }; + } else if (!$this->errorFormatter) { + $errorFormatter = function($e) { + return FormattedError::createFromException($e, false); + }; + } else { + $errorFormatter = $this->errorFormatter; + } + $result['errors'] = array_map($errorFormatter, $this->errors); } if (null !== $this->data) { diff --git a/tests/Executor/AbstractPromiseTest.php b/tests/Executor/AbstractPromiseTest.php index 6f1a24c..61347a1 100644 --- a/tests/Executor/AbstractPromiseTest.php +++ b/tests/Executor/AbstractPromiseTest.php @@ -2,6 +2,7 @@ namespace GraphQL\Tests\Executor; use GraphQL\Deferred; +use GraphQL\Error\UserError; use GraphQL\Error\Warning; use GraphQL\GraphQL; use GraphQL\Schema; @@ -120,7 +121,7 @@ class AbstractPromiseTest extends \PHPUnit_Framework_TestCase 'interfaces' => [$PetType], 'isTypeOf' => function () { return new Deferred(function () { - throw new \Exception('We are testing this error'); + throw new UserError('We are testing this error'); }); }, 'fields' => [ @@ -578,7 +579,7 @@ class AbstractPromiseTest extends \PHPUnit_Framework_TestCase 'name' => 'Pet', 'resolveType' => function () { return new Deferred(function () { - throw new \Exception('We are testing this error'); + throw new UserError('We are testing this error'); }); }, 'fields' => [ diff --git a/tests/Executor/ExecutorTest.php b/tests/Executor/ExecutorTest.php index 5121234..24120de 100644 --- a/tests/Executor/ExecutorTest.php +++ b/tests/Executor/ExecutorTest.php @@ -5,6 +5,7 @@ require_once __DIR__ . '/TestClasses.php'; use GraphQL\Deferred; use GraphQL\Error\Error; +use GraphQL\Error\UserError; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Schema; @@ -371,55 +372,55 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase return 'sync'; }, 'syncError' => function () { - throw new \Exception('Error getting syncError'); + throw new UserError('Error getting syncError'); }, 'syncRawError' => function() { - throw new \Exception('Error getting syncRawError'); + throw new UserError('Error getting syncRawError'); }, // inherited from JS reference implementation, but make no sense in this PHP impl // leaving it just to simplify migrations from newer js versions 'syncReturnError' => function() { - return new \Exception('Error getting syncReturnError'); + return new UserError('Error getting syncReturnError'); }, 'syncReturnErrorList' => function () { return [ 'sync0', - new \Exception('Error getting syncReturnErrorList1'), + new UserError('Error getting syncReturnErrorList1'), 'sync2', - new \Exception('Error getting syncReturnErrorList3') + new UserError('Error getting syncReturnErrorList3') ]; }, 'async' => function() { return new Deferred(function() { return 'async'; }); }, 'asyncReject' => function() { - return new Deferred(function() { throw new \Exception('Error getting asyncReject'); }); + return new Deferred(function() { throw new UserError('Error getting asyncReject'); }); }, 'asyncRawReject' => function () { return new Deferred(function() { - throw new \Exception('Error getting asyncRawReject'); + throw new UserError('Error getting asyncRawReject'); }); }, 'asyncEmptyReject' => function () { return new Deferred(function() { - throw new \Exception(); + throw new UserError(); }); }, 'asyncError' => function() { return new Deferred(function() { - throw new \Exception('Error getting asyncError'); + throw new UserError('Error getting asyncError'); }); }, // inherited from JS reference implementation, but make no sense in this PHP impl // leaving it just to simplify migrations from newer js versions 'asyncRawError' => function() { return new Deferred(function() { - throw new \Exception('Error getting asyncRawError'); + throw new UserError('Error getting asyncRawError'); }); }, 'asyncReturnError' => function() { return new Deferred(function() { - throw new \Exception('Error getting asyncReturnError'); + throw new UserError('Error getting asyncReturnError'); }); }, ]; diff --git a/tests/Executor/ListsTest.php b/tests/Executor/ListsTest.php index 7cb98bb..ba6b4ed 100644 --- a/tests/Executor/ListsTest.php +++ b/tests/Executor/ListsTest.php @@ -3,6 +3,7 @@ namespace GraphQL\Tests\Executor; use GraphQL\Deferred; +use GraphQL\Error\UserError; use GraphQL\Executor\Executor; use GraphQL\Error\FormattedError; use GraphQL\Language\Parser; @@ -72,7 +73,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase $this->checkHandlesNullableLists( function () { return new Deferred(function () { - throw new \Exception('bad'); + throw new UserError('bad'); }); }, [ @@ -131,7 +132,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase return 1; }), new Deferred(function() { - throw new \Exception('bad'); + throw new UserError('bad'); }), new Deferred(function() { return 2; @@ -174,12 +175,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase [ 'data' => [ 'nest' => null ], 'errors' => [ - FormattedError::create( - 'Cannot return null for non-nullable field DataType.test.', - [ new SourceLocation(1, 10) ] - ) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'locations' => [['line' => 1, 'column' => 10]] + ] ] - ] + ], + true ); } @@ -210,19 +212,20 @@ class ListsTest extends \PHPUnit_Framework_TestCase [ 'data' => [ 'nest' => null ], 'errors' => [ - FormattedError::create( - 'Cannot return null for non-nullable field DataType.test.', - [ new SourceLocation(1, 10) ] - ) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'locations' => [['line' => 1, 'column' => 10]] + ] ] - ] + ], + true ); // Rejected $this->checkHandlesNonNullableLists( function () { return new Deferred(function() { - throw new \Exception('bad'); + throw new UserError('bad'); }); }, [ @@ -280,7 +283,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase return 1; }), new Deferred(function() { - throw new \Exception('bad'); + throw new UserError('bad'); }), new Deferred(function() { return 2; @@ -317,12 +320,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase [ 'data' => [ 'nest' => [ 'test' => null ] ], 'errors' => [ - FormattedError::create( - 'Cannot return null for non-nullable field DataType.test.', - [ new SourceLocation(1, 10) ] - ) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'locations' => [ ['line' => 1, 'column' => 10] ] + ] ] - ] + ], + true ); // Returns null @@ -353,12 +357,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase [ 'data' => [ 'nest' => [ 'test' => null ] ], 'errors' => [ - FormattedError::create( - 'Cannot return null for non-nullable field DataType.test.', - [ new SourceLocation(1, 10) ] - ) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'locations' => [['line' => 1, 'column' => 10]] + ] ] - ] + ], + true ); // Returns null @@ -373,7 +378,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase $this->checkHandlesListOfNonNulls( function () { return new Deferred(function() { - throw new \Exception('bad'); + throw new UserError('bad'); }); }, [ @@ -425,7 +430,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase return 1; }), new Deferred(function() { - throw new \Exception('bad'); + throw new UserError('bad'); }), new Deferred(function() { return 2; @@ -463,12 +468,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase [ 'data' => [ 'nest' => null ], 'errors' => [ - FormattedError::create( - 'Cannot return null for non-nullable field DataType.test.', - [ new SourceLocation(1, 10) ] - ) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'locations' => [['line' => 1, 'column' => 10 ]] + ] ] - ] + ], + true ); // Returns null @@ -477,12 +483,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase [ 'data' => [ 'nest' => null ], 'errors' => [ - FormattedError::create( - 'Cannot return null for non-nullable field DataType.test.', - [ new SourceLocation(1, 10) ] - ) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'locations' => [ ['line' => 1, 'column' => 10] ] + ] ] - ] + ], + true ); } @@ -507,12 +514,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase [ 'data' => [ 'nest' => null ], 'errors' => [ - FormattedError::create( - 'Cannot return null for non-nullable field DataType.test.', - [ new SourceLocation(1, 10) ] - ) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'locations' => [ ['line' => 1, 'column' => 10] ] + ] ] - ] + ], + true ); // Returns null @@ -523,19 +531,20 @@ class ListsTest extends \PHPUnit_Framework_TestCase [ 'data' => [ 'nest' => null ], 'errors' => [ - FormattedError::create( - 'Cannot return null for non-nullable field DataType.test.', - [ new SourceLocation(1, 10) ] - ) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'locations' => [ ['line' => 1, 'column' => 10] ] + ] ] - ] + ], + true ); // Rejected $this->checkHandlesNonNullListOfNonNulls( function () { return new Deferred(function() { - throw new \Exception('bad'); + throw new UserError('bad'); }); }, [ @@ -586,12 +595,13 @@ class ListsTest extends \PHPUnit_Framework_TestCase [ 'data' => [ 'nest' => null ], 'errors' => [ - FormattedError::create( - 'Cannot return null for non-nullable field DataType.test.', - [ new SourceLocation(1, 10) ] - ) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.test.', + 'locations' => [['line' => 1, 'column' => 10]] + ] ] - ] + ], + true ); // Contains reject @@ -602,7 +612,7 @@ class ListsTest extends \PHPUnit_Framework_TestCase return 1; }), new Deferred(function() { - throw new \Exception('bad'); + throw new UserError('bad'); }), new Deferred(function() { return 2; @@ -628,25 +638,25 @@ class ListsTest extends \PHPUnit_Framework_TestCase $this->check($testType, $testData, $expected); } - private function checkHandlesNonNullableLists($testData, $expected) + private function checkHandlesNonNullableLists($testData, $expected, $debug = false) { $testType = Type::nonNull(Type::listOf(Type::int())); - $this->check($testType, $testData, $expected); + $this->check($testType, $testData, $expected, $debug); } - private function checkHandlesListOfNonNulls($testData, $expected) + private function checkHandlesListOfNonNulls($testData, $expected, $debug = false) { $testType = Type::listOf(Type::nonNull(Type::int())); - $this->check($testType, $testData, $expected); + $this->check($testType, $testData, $expected, $debug); } - public function checkHandlesNonNullListOfNonNulls($testData, $expected) + public function checkHandlesNonNullListOfNonNulls($testData, $expected, $debug = false) { $testType = Type::nonNull(Type::listOf(Type::nonNull(Type::int()))); - $this->check($testType, $testData, $expected); + $this->check($testType, $testData, $expected, $debug); } - private function check($testType, $testData, $expected) + private function check($testType, $testData, $expected, $debug = false) { $data = ['test' => $testData]; $dataType = null; @@ -675,6 +685,6 @@ class ListsTest extends \PHPUnit_Framework_TestCase $ast = Parser::parse('{ nest { test } }'); $result = Executor::execute($schema, $ast, $data); - $this->assertArraySubset($expected, $result->toArray()); + $this->assertArraySubset($expected, $result->toArray($debug)); } } diff --git a/tests/Executor/MutationsTest.php b/tests/Executor/MutationsTest.php index 3a580ba..7c63ccd 100644 --- a/tests/Executor/MutationsTest.php +++ b/tests/Executor/MutationsTest.php @@ -106,17 +106,17 @@ class MutationsTest extends \PHPUnit_Framework_TestCase 'sixth' => null, ], 'errors' => [ - FormattedError::create( - 'Cannot change the number', - [new SourceLocation(8, 7)] - ), - FormattedError::create( - 'Cannot change the number', - [new SourceLocation(17, 7)] - ) + [ + 'debugMessage' => 'Cannot change the number', + 'locations' => [['line' => 8, 'column' => 7]] + ], + [ + 'debugMessage' => 'Cannot change the number', + 'locations' => [['line' => 17, 'column' => 7]] + ] ] ]; - $this->assertArraySubset($expected, $mutationResult->toArray()); + $this->assertArraySubset($expected, $mutationResult->toArray(true)); } private function schema() diff --git a/tests/Executor/NonNullTest.php b/tests/Executor/NonNullTest.php index 0e91efa..1519f78 100644 --- a/tests/Executor/NonNullTest.php +++ b/tests/Executor/NonNullTest.php @@ -3,6 +3,7 @@ namespace GraphQL\Tests\Executor; use GraphQL\Deferred; +use GraphQL\Error\UserError; use GraphQL\Executor\Executor; use GraphQL\Error\FormattedError; use GraphQL\Language\Parser; @@ -31,10 +32,10 @@ class NonNullTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->syncError = new \Exception('sync'); - $this->nonNullSyncError = new \Exception('nonNullSync'); - $this->promiseError = new \Exception('promise'); - $this->nonNullPromiseError = new \Exception('nonNullPromise'); + $this->syncError = new UserError('sync'); + $this->nonNullSyncError = new UserError('nonNullSync'); + $this->promiseError = new UserError('promise'); + $this->nonNullPromiseError = new UserError('nonNullPromise'); $this->throwingData = [ 'sync' => function () { @@ -482,10 +483,13 @@ class NonNullTest extends \PHPUnit_Framework_TestCase 'nest' => null ], 'errors' => [ - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullSync.', [new SourceLocation(4, 11)]) + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullSync.', + 'locations' => [['line' => 4, 'column' => 11]] + ] ] ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); + $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true)); } public function testNullsASynchronouslyReturnedObjectThatContainsANonNullableFieldThatReturnsNullInAPromise() @@ -505,11 +509,17 @@ class NonNullTest extends \PHPUnit_Framework_TestCase 'nest' => null, ], 'errors' => [ - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(4, 11)]), + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullPromise.', + 'locations' => [['line' => 4, 'column' => 11]] + ], ] ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + ); } public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatReturnsNullSynchronously() @@ -529,11 +539,17 @@ class NonNullTest extends \PHPUnit_Framework_TestCase 'promiseNest' => null, ], 'errors' => [ - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullSync.', [new SourceLocation(4, 11)]), + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullSync.', + 'locations' => [['line' => 4, 'column' => 11]] + ], ] ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + ); } public function testNullsAnObjectReturnedInAPromiseThatContainsANonNullableFieldThatReturnsNullInaAPromise() @@ -553,11 +569,17 @@ class NonNullTest extends \PHPUnit_Framework_TestCase 'promiseNest' => null, ], 'errors' => [ - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(4, 11)]), + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullPromise.', + 'locations' => [['line' => 4, 'column' => 11]] + ], ] ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + ); } public function testNullsAComplexTreeOfNullableFieldsThatReturnNull() @@ -687,14 +709,17 @@ class NonNullTest extends \PHPUnit_Framework_TestCase 'anotherPromiseNest' => null, ], 'errors' => [ - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullSync.', [new SourceLocation(8, 19)]), - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullSync.', [new SourceLocation(19, 19)]), - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(30, 19)]), - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(41, 19)]), + ['debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullSync.', 'locations' => [ ['line' => 8, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullSync.', 'locations' => [ ['line' => 19, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullPromise.', 'locations' => [ ['line' => 30, 'column' => 19]]], + ['debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullPromise.', 'locations' => [ ['line' => 41, 'column' => 19]]], ] ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + ); } /** @@ -743,10 +768,16 @@ class NonNullTest extends \PHPUnit_Framework_TestCase $expected = [ 'errors' => [ - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullSync.', [new SourceLocation(2, 17)]), + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullSync.', + 'locations' => [['line' => 2, 'column' => 17]] + ], ] ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, Parser::parse($doc), $this->nullingData)->toArray(true) + ); } public function testNullsTheTopLevelIfAsyncNonNullableFieldResolvesNull() @@ -760,10 +791,16 @@ class NonNullTest extends \PHPUnit_Framework_TestCase $expected = [ 'data' => null, 'errors' => [ - FormattedError::create('Cannot return null for non-nullable field DataType.nonNullPromise.', [new SourceLocation(2, 17)]), + [ + 'debugMessage' => 'Cannot return null for non-nullable field DataType.nonNullPromise.', + 'locations' => [['line' => 2, 'column' => 17]] + ], ] ]; - $this->assertArraySubset($expected, Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray()); + $this->assertArraySubset( + $expected, + Executor::execute($this->schema, $ast, $this->nullingData, null, [], 'Q')->toArray(true) + ); } } diff --git a/tests/Server/QueryExecutionTest.php b/tests/Server/QueryExecutionTest.php index f2a71aa..e63d75b 100644 --- a/tests/Server/QueryExecutionTest.php +++ b/tests/Server/QueryExecutionTest.php @@ -48,7 +48,7 @@ class QueryExecutionTest extends \PHPUnit_Framework_TestCase 'fieldWithException' => [ 'type' => Type::string(), 'resolve' => function($root, $args, $context, $info) { - throw new \Exception("This is the exception we want"); + throw new UserError("This is the exception we want"); } ], 'testContextAndRootValue' => [ @@ -122,10 +122,10 @@ class QueryExecutionTest extends \PHPUnit_Framework_TestCase ], 'extensions' => [ 'phpErrors' => [ - ['message' => 'deprecated', 'severity' => 16384], - ['message' => 'notice', 'severity' => 1024], - ['message' => 'warning', 'severity' => 512], - ['message' => 'Undefined index: test', 'severity' => 8], + ['debugMessage' => 'deprecated', 'severity' => 16384], + ['debugMessage' => 'notice', 'severity' => 1024], + ['debugMessage' => 'warning', 'severity' => 512], + ['debugMessage' => 'Undefined index: test', 'severity' => 8], ] ] ]; @@ -518,7 +518,7 @@ class QueryExecutionTest extends \PHPUnit_Framework_TestCase private function assertQueryResultEquals($expected, $query, $variables = null) { $result = $this->executeQuery($query, $variables); - $this->assertArraySubset($expected, $result->toArray()); + $this->assertArraySubset($expected, $result->toArray(true)); return $result; } } diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index d47e5e9..2e9e8af 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -6,9 +6,6 @@ use GraphQL\Error\UserError; use GraphQL\Server\Helper; use GraphQL\Server\OperationParams; -/** - * @backupGlobals enabled - */ class RequestParsingTest extends \PHPUnit_Framework_TestCase { public function testParsesGraphqlRequest() diff --git a/tests/ServerTest.php b/tests/ServerTest.php index c334dc8..0ae8eb0 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -3,6 +3,7 @@ namespace GraphQL\Tests; use GraphQL\Error\FormattedError; use GraphQL\Error\InvariantViolation; +use GraphQL\Error\UserError; use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter; use GraphQL\Schema; use GraphQL\Server; @@ -465,7 +466,7 @@ class ServerTest extends \PHPUnit_Framework_TestCase 'withException' => [ 'type' => Type::string(), 'resolve' => function() { - throw new \Exception("Error"); + throw new UserError("Error"); } ] ] diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index 158d025..ecce357 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -415,12 +415,12 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase */ public function testMayBeInternallyRepresentedWithComplexValues() { - $result = GraphQL::execute($this->schema, '{ + $result = GraphQL::executeAndReturnResult($this->schema, '{ first: complexEnum second: complexEnum(fromEnum: TWO) good: complexEnum(provideGoodValue: true) bad: complexEnum(provideBadValue: true) - }'); + }')->toArray(true); $expected = [ 'data' => [ @@ -430,7 +430,7 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase 'bad' => null ], 'errors' => [[ - 'message' => + 'debugMessage' => 'Expected a value of type "Complex" but received: instance of ArrayObject', 'locations' => [['line' => 5, 'column' => 9]] ]] @@ -460,11 +460,11 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase [ 'data' => ['first' => 'ONE', 'second' => 'TWO', 'third' => null], 'errors' => [[ - 'message' => 'Expected a value of type "SimpleEnum" but received: "WRONG"', + 'debugMessage' => 'Expected a value of type "SimpleEnum" but received: "WRONG"', 'locations' => [['line' => 4, 'column' => 13]] ]] ], - GraphQL::execute($this->schema, $q) + GraphQL::executeAndReturnResult($this->schema, $q)->toArray(true) ); } diff --git a/tests/Type/ScalarSerializationTest.php b/tests/Type/ScalarSerializationTest.php index 2ba3411..d554ccf 100644 --- a/tests/Type/ScalarSerializationTest.php +++ b/tests/Type/ScalarSerializationTest.php @@ -117,14 +117,14 @@ class ScalarSerializationTest extends \PHPUnit_Framework_TestCase try { $floatType->serialize('one'); $this->fail('Expected exception was not thrown'); - } catch (UserError $e) { + } catch (InvariantViolation $e) { $this->assertEquals('Float cannot represent non numeric value: "one"', $e->getMessage()); } try { $floatType->serialize(''); $this->fail('Expected exception was not thrown'); - } catch (UserError $e) { + } catch (InvariantViolation $e) { $this->assertEquals('Float cannot represent non numeric value: (empty string)', $e->getMessage()); } @@ -149,14 +149,14 @@ class ScalarSerializationTest extends \PHPUnit_Framework_TestCase try { $stringType->serialize([]); $this->fail('Expected exception was not thrown'); - } catch (UserError $e) { - $this->assertEquals('String cannot represent non scalar value: array', $e->getMessage()); + } catch (InvariantViolation $e) { + $this->assertEquals('String cannot represent non scalar value: array(0)', $e->getMessage()); } try { $stringType->serialize(new \stdClass()); $this->fail('Expected exception was not thrown'); - } catch (UserError $e) { + } catch (InvariantViolation $e) { $this->assertEquals('String cannot represent non scalar value: instance of stdClass', $e->getMessage()); } }