mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 12:56:05 +03:00
Enabled GraphQL\Error to include path to failed value during execution step (not location in source query, but exact path to value, including index in array, etc) + tests for errors
This commit is contained in:
parent
c0f7ec099d
commit
a94640f9d2
110
src/Error.php
110
src/Error.php
@ -2,36 +2,66 @@
|
|||||||
namespace GraphQL;
|
namespace GraphQL;
|
||||||
|
|
||||||
use GraphQL\Language\Source;
|
use GraphQL\Language\Source;
|
||||||
|
use GraphQL\Language\SourceLocation;
|
||||||
|
|
||||||
// /graphql-js/src/error/GraphQLError.js
|
|
||||||
|
|
||||||
class Error extends \Exception
|
/**
|
||||||
|
* Class Error
|
||||||
|
* A GraphQLError describes an Error found during the parse, validate, or
|
||||||
|
* execute phases of performing a GraphQL operation. In addition to a message
|
||||||
|
* and stack trace, it also includes information about the locations in a
|
||||||
|
* GraphQL document and/or execution result that correspond to the Error.
|
||||||
|
*
|
||||||
|
* @package GraphQL
|
||||||
|
*/
|
||||||
|
class Error extends \Exception implements \JsonSerializable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
* A message describing the Error for debugging purposes.
|
||||||
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $message;
|
public $message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* An array of [ line => x, column => y] locations within the source GraphQL document
|
||||||
|
* which correspond to this error.
|
||||||
|
*
|
||||||
|
* Errors during validation often contain multiple locations, for example to
|
||||||
|
* point out two things with the same name. Errors during execution include a
|
||||||
|
* single location, the field which produced the error.
|
||||||
|
*
|
||||||
|
* @var SourceLocation[]
|
||||||
|
*/
|
||||||
|
private $locations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array describing the JSON-path into the execution response which
|
||||||
|
* corresponds to this error. Only included for errors during execution.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of GraphQL AST Nodes corresponding to this error.
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public $nodes;
|
public $nodes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* The source GraphQL document corresponding to this error.
|
||||||
*/
|
*
|
||||||
private $positions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<SourceLocation>
|
|
||||||
*/
|
|
||||||
private $locations;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Source|null
|
* @var Source|null
|
||||||
*/
|
*/
|
||||||
private $source;
|
private $source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $positions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an arbitrary Error, presumably thrown while attempting to execute a
|
* Given an arbitrary Error, presumably thrown while attempting to execute a
|
||||||
* GraphQL operation, produce a new GraphQLError aware of the location in the
|
* GraphQL operation, produce a new GraphQLError aware of the location in the
|
||||||
@ -39,10 +69,15 @@ class Error extends \Exception
|
|||||||
*
|
*
|
||||||
* @param $error
|
* @param $error
|
||||||
* @param array|null $nodes
|
* @param array|null $nodes
|
||||||
|
* @param array|null $path
|
||||||
* @return Error
|
* @return Error
|
||||||
*/
|
*/
|
||||||
public static function createLocatedError($error, $nodes = null)
|
public static function createLocatedError($error, $nodes = null, $path = null)
|
||||||
{
|
{
|
||||||
|
if ($error instanceof self) {
|
||||||
|
return $error;
|
||||||
|
}
|
||||||
|
|
||||||
if ($error instanceof \Exception) {
|
if ($error instanceof \Exception) {
|
||||||
$message = $error->getMessage();
|
$message = $error->getMessage();
|
||||||
$previous = $error;
|
$previous = $error;
|
||||||
@ -51,7 +86,7 @@ class Error extends \Exception
|
|||||||
$previous = null;
|
$previous = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Error($message, $nodes, $previous);
|
return new Error($message, $nodes, null, null, $path, $previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,12 +99,14 @@ class Error extends \Exception
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string|\Exception $message
|
* @param string $message
|
||||||
* @param array|null $nodes
|
* @param array|null $nodes
|
||||||
* @param Source $source
|
* @param Source $source
|
||||||
* @param null $positions
|
* @param array|null $positions
|
||||||
|
* @param array|null $path
|
||||||
|
* @param \Exception $previous
|
||||||
*/
|
*/
|
||||||
public function __construct($message, $nodes = null, \Exception $previous = null, Source $source = null, $positions = null)
|
public function __construct($message, $nodes = null, Source $source = null, $positions = null, $path = null, \Exception $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, 0, $previous);
|
parent::__construct($message, 0, $previous);
|
||||||
|
|
||||||
@ -80,6 +117,7 @@ class Error extends \Exception
|
|||||||
$this->nodes = $nodes;
|
$this->nodes = $nodes;
|
||||||
$this->source = $source;
|
$this->source = $source;
|
||||||
$this->positions = $positions;
|
$this->positions = $positions;
|
||||||
|
$this->path = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,14 +141,14 @@ class Error extends \Exception
|
|||||||
if (null === $this->positions) {
|
if (null === $this->positions) {
|
||||||
if (!empty($this->nodes)) {
|
if (!empty($this->nodes)) {
|
||||||
$positions = array_map(function($node) { return isset($node->loc) ? $node->loc->start : null; }, $this->nodes);
|
$positions = array_map(function($node) { return isset($node->loc) ? $node->loc->start : null; }, $this->nodes);
|
||||||
$this->positions = array_filter($positions);
|
$this->positions = array_filter($positions, function($p) {return $p !== null;});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $this->positions;
|
return $this->positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<SourceLocation>
|
* @return SourceLocation[]
|
||||||
*/
|
*/
|
||||||
public function getLocations()
|
public function getLocations()
|
||||||
{
|
{
|
||||||
@ -129,4 +167,38 @@ class Error extends \Exception
|
|||||||
|
|
||||||
return $this->locations;
|
return $this->locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns array representation of error suitable for serialization
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toSerializableArray()
|
||||||
|
{
|
||||||
|
$arr = [
|
||||||
|
'message' => $this->getMessage(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$locations = Utils::map($this->getLocations(), function(SourceLocation $loc) {return $loc->toArray();});
|
||||||
|
if (!empty($locations)) {
|
||||||
|
$arr['locations'] = $locations;
|
||||||
|
}
|
||||||
|
if (!empty($this->path)) {
|
||||||
|
$arr['path'] = $this->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify data which should be serialized to JSON
|
||||||
|
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||||
|
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||||
|
* which is a value of any type other than a resource.
|
||||||
|
* @since 5.4.0
|
||||||
|
*/
|
||||||
|
function jsonSerialize()
|
||||||
|
{
|
||||||
|
return $this->toSerializableArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,11 +166,12 @@ class Executor
|
|||||||
$type = self::getOperationRootType($exeContext->schema, $operation);
|
$type = self::getOperationRootType($exeContext->schema, $operation);
|
||||||
$fields = self::collectFields($exeContext, $type, $operation->selectionSet, new \ArrayObject(), new \ArrayObject());
|
$fields = self::collectFields($exeContext, $type, $operation->selectionSet, new \ArrayObject(), new \ArrayObject());
|
||||||
|
|
||||||
|
$path = [];
|
||||||
if ($operation->operation === 'mutation') {
|
if ($operation->operation === 'mutation') {
|
||||||
return self::executeFieldsSerially($exeContext, $type, $rootValue, $fields);
|
return self::executeFieldsSerially($exeContext, $type, $rootValue, $path, $fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::executeFields($exeContext, $type, $rootValue, $fields);
|
return self::executeFields($exeContext, $type, $rootValue, $path, $fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -217,11 +218,12 @@ class Executor
|
|||||||
* Implements the "Evaluating selection sets" section of the spec
|
* Implements the "Evaluating selection sets" section of the spec
|
||||||
* for "write" mode.
|
* for "write" mode.
|
||||||
*/
|
*/
|
||||||
private static function executeFieldsSerially(ExecutionContext $exeContext, ObjectType $parentType, $sourceValue, $fields)
|
private static function executeFieldsSerially(ExecutionContext $exeContext, ObjectType $parentType, $sourceValue, $path, $fields)
|
||||||
{
|
{
|
||||||
$results = [];
|
$results = [];
|
||||||
foreach ($fields as $responseName => $fieldASTs) {
|
foreach ($fields as $responseName => $fieldASTs) {
|
||||||
$result = self::resolveField($exeContext, $parentType, $sourceValue, $fieldASTs);
|
$path[] = $responseName;
|
||||||
|
$result = self::resolveField($exeContext, $parentType, $sourceValue, $fieldASTs, $path);
|
||||||
|
|
||||||
if ($result !== self::$UNDEFINED) {
|
if ($result !== self::$UNDEFINED) {
|
||||||
// Undefined means that field is not defined in schema
|
// Undefined means that field is not defined in schema
|
||||||
@ -235,11 +237,11 @@ class Executor
|
|||||||
* Implements the "Evaluating selection sets" section of the spec
|
* Implements the "Evaluating selection sets" section of the spec
|
||||||
* for "read" mode.
|
* for "read" mode.
|
||||||
*/
|
*/
|
||||||
private static function executeFields(ExecutionContext $exeContext, ObjectType $parentType, $source, $fields)
|
private static function executeFields(ExecutionContext $exeContext, ObjectType $parentType, $source, $path, $fields)
|
||||||
{
|
{
|
||||||
// Native PHP doesn't support promises.
|
// Native PHP doesn't support promises.
|
||||||
// Custom executor should be built for platforms like ReactPHP
|
// Custom executor should be built for platforms like ReactPHP
|
||||||
return self::executeFieldsSerially($exeContext, $parentType, $source, $fields);
|
return self::executeFieldsSerially($exeContext, $parentType, $source, $path, $fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -387,7 +389,7 @@ class Executor
|
|||||||
* then calls completeValue to complete promises, serialize scalars, or execute
|
* then calls completeValue to complete promises, serialize scalars, or execute
|
||||||
* the sub-selection-set for objects.
|
* the sub-selection-set for objects.
|
||||||
*/
|
*/
|
||||||
private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $source, $fieldASTs)
|
private static function resolveField(ExecutionContext $exeContext, ObjectType $parentType, $source, $fieldASTs, $path)
|
||||||
{
|
{
|
||||||
$fieldAST = $fieldASTs[0];
|
$fieldAST = $fieldASTs[0];
|
||||||
|
|
||||||
@ -416,6 +418,7 @@ class Executor
|
|||||||
'fieldASTs' => $fieldASTs,
|
'fieldASTs' => $fieldASTs,
|
||||||
'returnType' => $returnType,
|
'returnType' => $returnType,
|
||||||
'parentType' => $parentType,
|
'parentType' => $parentType,
|
||||||
|
'path' => $path,
|
||||||
'schema' => $exeContext->schema,
|
'schema' => $exeContext->schema,
|
||||||
'fragments' => $exeContext->fragments,
|
'fragments' => $exeContext->fragments,
|
||||||
'rootValue' => $exeContext->rootValue,
|
'rootValue' => $exeContext->rootValue,
|
||||||
@ -446,6 +449,7 @@ class Executor
|
|||||||
$returnType,
|
$returnType,
|
||||||
$fieldASTs,
|
$fieldASTs,
|
||||||
$info,
|
$info,
|
||||||
|
$path,
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -463,34 +467,96 @@ class Executor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a small wrapper around completeValue which detects and logs errors
|
/**
|
||||||
// in the execution context.
|
* This is a small wrapper around completeValue which detects and logs errors
|
||||||
public static function completeValueCatchingError(
|
* in the execution context.
|
||||||
|
*
|
||||||
|
* @param ExecutionContext $exeContext
|
||||||
|
* @param Type $returnType
|
||||||
|
* @param $fieldASTs
|
||||||
|
* @param ResolveInfo $info
|
||||||
|
* @param $path
|
||||||
|
* @param $result
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
private static function completeValueCatchingError(
|
||||||
ExecutionContext $exeContext,
|
ExecutionContext $exeContext,
|
||||||
Type $returnType,
|
Type $returnType,
|
||||||
$fieldASTs,
|
$fieldASTs,
|
||||||
ResolveInfo $info,
|
ResolveInfo $info,
|
||||||
|
$path,
|
||||||
$result
|
$result
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// If the field type is non-nullable, then it is resolved without any
|
// If the field type is non-nullable, then it is resolved without any
|
||||||
// protection from errors.
|
// protection from errors.
|
||||||
if ($returnType instanceof NonNull) {
|
if ($returnType instanceof NonNull) {
|
||||||
return self::completeValue($exeContext, $returnType, $fieldASTs, $info, $result);
|
return self::completeValueWithLocatedError(
|
||||||
|
$exeContext,
|
||||||
|
$returnType,
|
||||||
|
$fieldASTs,
|
||||||
|
$info,
|
||||||
|
$path,
|
||||||
|
$result
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, error protection is applied, logging the error and resolving
|
// Otherwise, error protection is applied, logging the error and resolving
|
||||||
// a null value for this field if one is encountered.
|
// a null value for this field if one is encountered.
|
||||||
try {
|
try {
|
||||||
return self::completeValue($exeContext, $returnType, $fieldASTs, $info, $result);
|
return self::completeValueWithLocatedError(
|
||||||
|
$exeContext,
|
||||||
|
$returnType,
|
||||||
|
$fieldASTs,
|
||||||
|
$info,
|
||||||
|
$path,
|
||||||
|
$result
|
||||||
|
);
|
||||||
} catch (Error $err) {
|
} catch (Error $err) {
|
||||||
// If `completeValue` returned abruptly (threw an error), log the error
|
// If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
|
||||||
// and return null.
|
// and return null.
|
||||||
$exeContext->addError($err);
|
$exeContext->addError($err);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a small wrapper around completeValue which annotates errors with
|
||||||
|
* location information.
|
||||||
|
*
|
||||||
|
* @param ExecutionContext $exeContext
|
||||||
|
* @param Type $returnType
|
||||||
|
* @param $fieldASTs
|
||||||
|
* @param ResolveInfo $info
|
||||||
|
* @param $path
|
||||||
|
* @param $result
|
||||||
|
* @return array|null
|
||||||
|
* @throws Error
|
||||||
|
*/
|
||||||
|
static function completeValueWithLocatedError(
|
||||||
|
ExecutionContext $exeContext,
|
||||||
|
Type $returnType,
|
||||||
|
$fieldASTs,
|
||||||
|
ResolveInfo $info,
|
||||||
|
$path,
|
||||||
|
$result
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return self::completeValue(
|
||||||
|
$exeContext,
|
||||||
|
$returnType,
|
||||||
|
$fieldASTs,
|
||||||
|
$info,
|
||||||
|
$path,
|
||||||
|
$result
|
||||||
|
);
|
||||||
|
} catch (\Exception $error) {
|
||||||
|
throw Error::createLocatedError($error, $fieldASTs, $path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the instructions for completeValue as defined in the
|
* Implements the instructions for completeValue as defined in the
|
||||||
* "Field entries" section of the spec.
|
* "Field entries" section of the spec.
|
||||||
@ -516,15 +582,23 @@ class Executor
|
|||||||
* @param Type $returnType
|
* @param Type $returnType
|
||||||
* @param Field[] $fieldASTs
|
* @param Field[] $fieldASTs
|
||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
|
* @param array $path
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return array|null
|
* @return array|null
|
||||||
* @throws Error
|
* @throws Error
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private static function completeValue(ExecutionContext $exeContext, Type $returnType, $fieldASTs, ResolveInfo $info, &$result)
|
private static function completeValue(
|
||||||
|
ExecutionContext $exeContext,
|
||||||
|
Type $returnType,
|
||||||
|
$fieldASTs,
|
||||||
|
ResolveInfo $info,
|
||||||
|
$path,
|
||||||
|
&$result
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if ($result instanceof \Exception) {
|
if ($result instanceof \Exception) {
|
||||||
throw Error::createLocatedError($result, $fieldASTs);
|
throw $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If field type is NonNull, complete for inner type, and throw field error
|
// If field type is NonNull, complete for inner type, and throw field error
|
||||||
@ -535,12 +609,12 @@ class Executor
|
|||||||
$returnType->getWrappedType(),
|
$returnType->getWrappedType(),
|
||||||
$fieldASTs,
|
$fieldASTs,
|
||||||
$info,
|
$info,
|
||||||
|
$path,
|
||||||
$result
|
$result
|
||||||
);
|
);
|
||||||
if ($completed === null) {
|
if ($completed === null) {
|
||||||
throw new Error(
|
throw new \UnexpectedValueException(
|
||||||
'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.',
|
'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.'
|
||||||
$fieldASTs instanceof \ArrayObject ? $fieldASTs->getArrayCopy() : $fieldASTs
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return $completed;
|
return $completed;
|
||||||
@ -553,7 +627,7 @@ class Executor
|
|||||||
|
|
||||||
// If field type is List, complete each item in the list with the inner type
|
// If field type is List, complete each item in the list with the inner type
|
||||||
if ($returnType instanceof ListOfType) {
|
if ($returnType instanceof ListOfType) {
|
||||||
return self::completeListValue($exeContext, $returnType, $fieldASTs, $info, $result);
|
return self::completeListValue($exeContext, $returnType, $fieldASTs, $info, $path, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If field type is Scalar or Enum, serialize to a valid value, returning
|
// If field type is Scalar or Enum, serialize to a valid value, returning
|
||||||
@ -564,15 +638,15 @@ class Executor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($returnType instanceof AbstractType) {
|
if ($returnType instanceof AbstractType) {
|
||||||
return self::completeAbstractValue($exeContext, $returnType, $fieldASTs, $info, $result);
|
return self::completeAbstractValue($exeContext, $returnType, $fieldASTs, $info, $path, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field type must be Object, Interface or Union and expect sub-selections.
|
// Field type must be Object, Interface or Union and expect sub-selections.
|
||||||
if ($returnType instanceof ObjectType) {
|
if ($returnType instanceof ObjectType) {
|
||||||
return self::completeObjectValue($exeContext, $returnType, $fieldASTs, $info, $result);
|
return self::completeObjectValue($exeContext, $returnType, $fieldASTs, $info, $path, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("Cannot complete value of unexpected type \"{$returnType}\".");
|
throw new \RuntimeException("Cannot complete value of unexpected type \"{$returnType}\".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -648,11 +722,12 @@ class Executor
|
|||||||
* @param AbstractType $returnType
|
* @param AbstractType $returnType
|
||||||
* @param $fieldASTs
|
* @param $fieldASTs
|
||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
|
* @param array $path
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return mixed
|
* @return mixed
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
private static function completeAbstractValue(ExecutionContext $exeContext, AbstractType $returnType, $fieldASTs, ResolveInfo $info, &$result)
|
private static function completeAbstractValue(ExecutionContext $exeContext, AbstractType $returnType, $fieldASTs, ResolveInfo $info, $path, &$result)
|
||||||
{
|
{
|
||||||
$resolveType = $returnType->getResolveTypeFn();
|
$resolveType = $returnType->getResolveTypeFn();
|
||||||
|
|
||||||
@ -675,7 +750,7 @@ class Executor
|
|||||||
$fieldASTs
|
$fieldASTs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return self::completeObjectValue($exeContext, $runtimeType, $fieldASTs, $info, $result);
|
return self::completeObjectValue($exeContext, $runtimeType, $fieldASTs, $info, $path, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -686,11 +761,12 @@ class Executor
|
|||||||
* @param ListOfType $returnType
|
* @param ListOfType $returnType
|
||||||
* @param $fieldASTs
|
* @param $fieldASTs
|
||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
|
* @param array $path
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return array
|
* @return array
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private static function completeListValue(ExecutionContext $exeContext, ListOfType $returnType, $fieldASTs, ResolveInfo $info, &$result)
|
private static function completeListValue(ExecutionContext $exeContext, ListOfType $returnType, $fieldASTs, ResolveInfo $info, $path, &$result)
|
||||||
{
|
{
|
||||||
$itemType = $returnType->getWrappedType();
|
$itemType = $returnType->getWrappedType();
|
||||||
Utils::invariant(
|
Utils::invariant(
|
||||||
@ -698,9 +774,11 @@ class Executor
|
|||||||
'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'
|
'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
$tmp = [];
|
$tmp = [];
|
||||||
foreach ($result as $item) {
|
foreach ($result as $item) {
|
||||||
$tmp[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $item);
|
$path[] = $i++;
|
||||||
|
$tmp[] = self::completeValueCatchingError($exeContext, $itemType, $fieldASTs, $info, $path, $item);
|
||||||
}
|
}
|
||||||
return $tmp;
|
return $tmp;
|
||||||
}
|
}
|
||||||
@ -727,11 +805,12 @@ class Executor
|
|||||||
* @param ObjectType $returnType
|
* @param ObjectType $returnType
|
||||||
* @param $fieldASTs
|
* @param $fieldASTs
|
||||||
* @param ResolveInfo $info
|
* @param ResolveInfo $info
|
||||||
|
* @param array $path
|
||||||
* @param $result
|
* @param $result
|
||||||
* @return array
|
* @return array
|
||||||
* @throws Error
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
private static function completeObjectValue(ExecutionContext $exeContext, ObjectType $returnType, $fieldASTs, ResolveInfo $info, &$result)
|
private static function completeObjectValue(ExecutionContext $exeContext, ObjectType $returnType, $fieldASTs, ResolveInfo $info, $path, &$result)
|
||||||
{
|
{
|
||||||
// If there is an isTypeOf predicate function, call it with the
|
// If there is an isTypeOf predicate function, call it with the
|
||||||
// current result. If isTypeOf returns false, then raise an error rather
|
// current result. If isTypeOf returns false, then raise an error rather
|
||||||
@ -768,6 +847,6 @@ class Executor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::executeFields($exeContext, $returnType, $result, $subFieldASTs);
|
return self::executeFields($exeContext, $returnType, $result, $path, $subFieldASTs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class SyntaxError extends Error
|
|||||||
"Syntax Error {$source->name} ({$location->line}:{$location->column}) $description\n\n" .
|
"Syntax Error {$source->name} ({$location->line}:{$location->column}) $description\n\n" .
|
||||||
self::highlightSourceAtLocation($source, $location);
|
self::highlightSourceAtLocation($source, $location);
|
||||||
|
|
||||||
parent::__construct($syntaxError, null, null, $source, [$position]);
|
parent::__construct($syntaxError, null, $source, [$position]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function highlightSourceAtLocation(Source $source, SourceLocation $location)
|
public static function highlightSourceAtLocation(Source $source, SourceLocation $location)
|
||||||
|
113
tests/ErrorTest.php
Normal file
113
tests/ErrorTest.php
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests;
|
||||||
|
|
||||||
|
use GraphQL\Error;
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
use GraphQL\Language\Source;
|
||||||
|
use GraphQL\Language\SourceLocation;
|
||||||
|
|
||||||
|
class ErrorTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @it uses the stack of an original error
|
||||||
|
*/
|
||||||
|
public function testUsesTheStackOfAnOriginalError()
|
||||||
|
{
|
||||||
|
$prev = new \Exception("Original");
|
||||||
|
$err = new Error('msg', null, null, null, null, $prev);
|
||||||
|
|
||||||
|
$this->assertSame($err->getPrevious(), $prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it converts nodes to positions and locations
|
||||||
|
*/
|
||||||
|
public function testConvertsNodesToPositionsAndLocations()
|
||||||
|
{
|
||||||
|
$source = new Source('{
|
||||||
|
field
|
||||||
|
}');
|
||||||
|
$ast = Parser::parse($source);
|
||||||
|
$fieldAST = $ast->definitions[0]->selectionSet->selections[0];
|
||||||
|
$e = new Error('msg', [ $fieldAST ]);
|
||||||
|
|
||||||
|
$this->assertEquals([$fieldAST], $e->nodes);
|
||||||
|
$this->assertEquals($source, $e->getSource());
|
||||||
|
$this->assertEquals([8], $e->getPositions());
|
||||||
|
$this->assertEquals([new SourceLocation(2, 7)], $e->getLocations());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it converts node with loc.start === 0 to positions and locations
|
||||||
|
*/
|
||||||
|
public function testConvertsNodeWithStart0ToPositionsAndLocations()
|
||||||
|
{
|
||||||
|
$source = new Source('{
|
||||||
|
field
|
||||||
|
}');
|
||||||
|
$ast = Parser::parse($source);
|
||||||
|
$operationAST = $ast->definitions[0];
|
||||||
|
$e = new Error('msg', [ $operationAST ]);
|
||||||
|
|
||||||
|
$this->assertEquals([$operationAST], $e->nodes);
|
||||||
|
$this->assertEquals($source, $e->getSource());
|
||||||
|
$this->assertEquals([0], $e->getPositions());
|
||||||
|
$this->assertEquals([new SourceLocation(1, 1)], $e->getLocations());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it converts source and positions to locations
|
||||||
|
*/
|
||||||
|
public function testConvertsSourceAndPositionsToLocations()
|
||||||
|
{
|
||||||
|
$source = new Source('{
|
||||||
|
field
|
||||||
|
}');
|
||||||
|
$e = new Error('msg', null, $source, [ 10 ]);
|
||||||
|
|
||||||
|
$this->assertEquals(null, $e->nodes);
|
||||||
|
$this->assertEquals($source, $e->getSource());
|
||||||
|
$this->assertEquals([10], $e->getPositions());
|
||||||
|
$this->assertEquals([new SourceLocation(2, 9)], $e->getLocations());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it serializes to include message
|
||||||
|
*/
|
||||||
|
public function testSerializesToIncludeMessage()
|
||||||
|
{
|
||||||
|
$e = new Error('msg');
|
||||||
|
$this->assertEquals(['message' => 'msg'], $e->toSerializableArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it serializes to include message and locations
|
||||||
|
*/
|
||||||
|
public function testSerializesToIncludeMessageAndLocations()
|
||||||
|
{
|
||||||
|
$node = Parser::parse('{ field }')->definitions[0]->selectionSet->selections[0];
|
||||||
|
$e = new Error('msg', [ $node ]);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
['message' => 'msg', 'locations' => [['line' => 1, 'column' => 3]]],
|
||||||
|
$e->toSerializableArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it serializes to include path
|
||||||
|
*/
|
||||||
|
public function testSerializesToIncludePath()
|
||||||
|
{
|
||||||
|
$e = new Error(
|
||||||
|
'msg',
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
[ 'path', 3, 'to', 'field' ]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals([ 'path', 3, 'to', 'field' ], $e->path);
|
||||||
|
$this->assertEquals(['message' => 'msg', 'path' => [ 'path', 3, 'to', 'field' ]], $e->toSerializableArray());
|
||||||
|
}
|
||||||
|
}
|
@ -352,7 +352,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
|||||||
return 'sync';
|
return 'sync';
|
||||||
},
|
},
|
||||||
'syncError' => function () {
|
'syncError' => function () {
|
||||||
throw new Error('Error getting syncError');
|
throw new \Exception('Error getting syncError');
|
||||||
},
|
},
|
||||||
'syncRawError' => function() {
|
'syncRawError' => function() {
|
||||||
throw new \Exception('Error getting syncRawError');
|
throw new \Exception('Error getting syncRawError');
|
||||||
|
@ -12,8 +12,10 @@ use GraphQL\Type\Definition\Type;
|
|||||||
|
|
||||||
class NonNullTest extends \PHPUnit_Framework_TestCase
|
class NonNullTest extends \PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
/** @var Error */
|
/** @var \Exception */
|
||||||
public $syncError;
|
public $syncError;
|
||||||
|
|
||||||
|
/** @var \Exception */
|
||||||
public $nonNullSyncError;
|
public $nonNullSyncError;
|
||||||
public $throwingData;
|
public $throwingData;
|
||||||
public $nullingData;
|
public $nullingData;
|
||||||
@ -21,8 +23,8 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
$this->syncError = new Error('sync');
|
$this->syncError = new \Exception('sync');
|
||||||
$this->nonNullSyncError = new Error('nonNullSync');
|
$this->nonNullSyncError = new \Exception('nonNullSync');
|
||||||
|
|
||||||
$this->throwingData = [
|
$this->throwingData = [
|
||||||
'sync' => function () {
|
'sync' => function () {
|
||||||
@ -92,7 +94,7 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
],
|
],
|
||||||
'errors' => [
|
'errors' => [
|
||||||
FormattedError::create(
|
FormattedError::create(
|
||||||
$this->syncError->message,
|
$this->syncError->getMessage(),
|
||||||
[new SourceLocation(3, 9)]
|
[new SourceLocation(3, 9)]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -118,7 +120,7 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
'nest' => null
|
'nest' => null
|
||||||
],
|
],
|
||||||
'errors' => [
|
'errors' => [
|
||||||
FormattedError::create($this->nonNullSyncError->message, [new SourceLocation(4, 11)])
|
FormattedError::create($this->nonNullSyncError->getMessage(), [new SourceLocation(4, 11)])
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||||
@ -149,8 +151,8 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
'errors' => [
|
'errors' => [
|
||||||
FormattedError::create($this->syncError->message, [new SourceLocation(4, 11)]),
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(4, 11)]),
|
||||||
FormattedError::create($this->syncError->message, [new SourceLocation(6, 13)]),
|
FormattedError::create($this->syncError->getMessage(), [new SourceLocation(6, 13)]),
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
$this->assertEquals($expected, Executor::execute($this->schema, $ast, $this->throwingData, null, [], 'Q')->toArray());
|
||||||
@ -243,7 +245,7 @@ class NonNullTest extends \PHPUnit_Framework_TestCase
|
|||||||
$expected = [
|
$expected = [
|
||||||
'data' => null,
|
'data' => null,
|
||||||
'errors' => [
|
'errors' => [
|
||||||
FormattedError::create($this->nonNullSyncError->message, [new SourceLocation(2, 17)])
|
FormattedError::create($this->nonNullSyncError->getMessage(), [new SourceLocation(2, 17)])
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray());
|
$this->assertEquals($expected, Executor::execute($this->schema, Parser::parse($doc), $this->throwingData)->toArray());
|
||||||
|
Loading…
Reference in New Issue
Block a user