mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-21 20:36:05 +03:00
Default error formatter now returns "Internal server error" unless error is client-aware and safe to report directly to end-users
This commit is contained in:
parent
fbcd20814a
commit
38922dbbed
48
UPGRADE.md
48
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
|
||||
|
@ -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.
|
||||
|
17
src/Error/ClientAware.php
Normal file
17
src/Error/ClientAware.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace GraphQL\Error;
|
||||
|
||||
/**
|
||||
* Interface ClientAware
|
||||
*
|
||||
* @package GraphQL\Error
|
||||
*/
|
||||
interface ClientAware
|
||||
{
|
||||
/**
|
||||
* Returns true when exception message is safe to be displayed to client
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClientSafe();
|
||||
}
|
@ -14,7 +14,7 @@ use GraphQL\Utils\Utils;
|
||||
*
|
||||
* @package GraphQL
|
||||
*/
|
||||
class Error extends \Exception implements \JsonSerializable
|
||||
class Error extends \Exception implements \JsonSerializable, ClientAware
|
||||
{
|
||||
/**
|
||||
* A message describing the Error for debugging purposes.
|
||||
@ -62,6 +62,11 @@ class Error extends \Exception implements \JsonSerializable
|
||||
*/
|
||||
private $positions;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isClientSafe;
|
||||
|
||||
/**
|
||||
* Given an arbitrary Error, presumably thrown while attempting to execute a
|
||||
* GraphQL operation, produce a new GraphQLError aware of the location in the
|
||||
@ -133,6 +138,22 @@ class Error extends \Exception implements \JsonSerializable
|
||||
$this->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()
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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' => [
|
||||
|
@ -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');
|
||||
});
|
||||
},
|
||||
];
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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");
|
||||
}
|
||||
]
|
||||
]
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user