Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
Fix handling of errors in mutations (#297)
Browse files Browse the repository at this point in the history
* Fix handling of errors in mutations

* Use instanceof to check for promises

* Fix error handling of \Throwable

* Fix handling of Throwables in mutations
  • Loading branch information
crisu83 authored Oct 8, 2018
1 parent 3acd24e commit 9fbb8cd
Showing 1 changed file with 48 additions and 35 deletions.
83 changes: 48 additions & 35 deletions src/Execution/Executor.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@
use Digia\GraphQL\Type\Definition\SerializableTypeInterface;
use Digia\GraphQL\Type\Definition\TypeInterface;
use Digia\GraphQL\Type\Definition\UnionType;
use InvalidArgumentException;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\FulfilledPromise;
use React\Promise\PromiseInterface;
use Throwable;
use function Digia\GraphQL\Type\SchemaMetaFieldDefinition;
use function Digia\GraphQL\Type\TypeMetaFieldDefinition;
use function Digia\GraphQL\Type\TypeNameMetaFieldDefinition;
Expand Down Expand Up @@ -72,6 +70,7 @@ class Executor

/**
* Executor constructor.
*
* @param ExecutionContext $context
* @param FieldCollector $fieldCollector
* @param ErrorHandlerInterface|null $errorHandler
Expand Down Expand Up @@ -115,7 +114,7 @@ public function execute(): ?array
? $this->executeFieldsSerially($objectType, $rootValue, $path, $fields)
: $this->executeFields($objectType, $rootValue, $path, $fields);
} catch (\Throwable $ex) {
$this->handleError(new ExecutionException($ex->getMessage(), $fields, null, null, null, null, $ex));
$this->handleError(new ExecutionException($ex->getMessage(), null, null, null, null, null, $ex));
return null;
}

Expand All @@ -125,6 +124,7 @@ public function execute(): ?array
/**
* @param Schema $schema
* @param OperationDefinitionNode $operation
*
* @return ObjectType|null
* @throws ExecutionException
*/
Expand All @@ -141,6 +141,7 @@ public function getOperationType(Schema $schema, OperationDefinitionNode $operat
[$operation]
);
}

return $mutationType;
case 'subscription':
$subscriptionType = $schema->getSubscriptionType();
Expand All @@ -150,6 +151,7 @@ public function getOperationType(Schema $schema, OperationDefinitionNode $operat
[$operation]
);
}

return $subscriptionType;
default:
throw new ExecutionException(
Expand All @@ -166,9 +168,8 @@ public function getOperationType(Schema $schema, OperationDefinitionNode $operat
* @param mixed $rootValue
* @param array $path
* @param array $fields
*
* @return array
* @throws InvalidArgumentException
* @throws Throwable
*/
public function executeFieldsSerially(
ObjectType $objectType,
Expand All @@ -189,10 +190,10 @@ public function executeFieldsSerially(
return null;
}

if ($this->isPromise($result)) {
/** @var ExtendedPromiseInterface $result */
if ($result instanceof ExtendedPromiseInterface) {
return $result->then(function ($resolvedResult) use ($fieldName, $results) {
$results[$fieldName] = $resolvedResult;

return $results;
});
}
Expand All @@ -217,8 +218,11 @@ public function executeFieldsSerially(

$promise->then(function ($resolvedResults) use (&$finalResults) {
$finalResults = $resolvedResults ?? [];
})->otherwise(function ($ex) {
$this->context->addError($ex);
})->otherwise(function (\Throwable $ex) {
if (!$ex instanceof ExecutionException) {
$ex = new ExecutionException($ex->getMessage(), null, null, null, null, null, $ex);
}
$this->handleError($ex);
});

return $finalResults;
Expand All @@ -228,6 +232,7 @@ public function executeFieldsSerially(
* @param Schema $schema
* @param ObjectType $parentType
* @param string $fieldName
*
* @return Field|null
*/
public function getFieldDefinition(
Expand All @@ -251,6 +256,7 @@ public function getFieldDefinition(
return $typeNameMetaFieldDefinition;
}

/** @noinspection PhpUnhandledExceptionInspection */
$fields = $parentType->getFields();

return $fields[$fieldName] ?? null;
Expand All @@ -262,6 +268,7 @@ public function getFieldDefinition(
* @param ResolveInfo $info
* @param array $path
* @param mixed $result
*
* @return array|mixed|null
* @throws \Throwable
*/
Expand Down Expand Up @@ -291,22 +298,22 @@ public function completeValueCatchingError(
$result
);

if ($this->isPromise($completed)) {
$context = $this->context;
/** @var ExtendedPromiseInterface $completed */
return $completed->then(null, function ($error) use ($context, $fieldNodes, $path) {
//@TODO Handle $error better
if ($completed instanceof ExtendedPromiseInterface) {
return $completed->then(null, function ($error) use ($fieldNodes, $path) {
if ($error instanceof \Exception) {
$context->addError($this->buildLocatedError($error, $fieldNodes, $path));
$this->handleError(
$this->buildLocatedError($error, $fieldNodes, $path)
);
} else {
$context->addError(
$this->handleError(
$this->buildLocatedError(
new ExecutionException($error ?? 'An unknown error occurred.'),
$fieldNodes,
$path
)
);
}

return new FulfilledPromise(null);
});
}
Expand All @@ -318,13 +325,13 @@ public function completeValueCatchingError(
}
}


/**
* @param TypeInterface $fieldType
* @param FieldNode[] $fieldNodes
* @param ResolveInfo $info
* @param array $path
* @param mixed $result
*
* @return array|mixed
* @throws \Throwable
*/
Expand Down Expand Up @@ -357,7 +364,9 @@ public function completeValueWithLocatedError(
* @param mixed $rootValue
* @param array $path
* @param array $fields
*
* @return array
* @throws ExecutionException
* @throws \Throwable
*/
protected function executeFields(
Expand All @@ -379,7 +388,7 @@ protected function executeFields(
continue;
}

$doesContainPromise = $doesContainPromise || $this->isPromise($result);
$doesContainPromise = $doesContainPromise || $result instanceof ExtendedPromiseInterface;

$finalResults[$fieldName] = $result;
}
Expand All @@ -404,9 +413,10 @@ protected function executeFields(
* @param mixed $rootValue
* @param FieldNode[] $fieldNodes
* @param array $path
*
* @return array|mixed|null
* @throws UndefinedException
* @throws Throwable
* @throws \Throwable
*/
protected function resolveField(
ObjectType $parentType,
Expand Down Expand Up @@ -450,6 +460,7 @@ protected function resolveField(
/**
* @param Field $field
* @param ObjectType $objectType
*
* @return callable|mixed|null
*/
protected function determineResolveCallback(Field $field, ObjectType $objectType)
Expand All @@ -471,6 +482,7 @@ protected function determineResolveCallback(Field $field, ObjectType $objectType
* @param ResolveInfo $info
* @param array $path
* @param mixed $result
*
* @return array|mixed
* @throws InvariantException
* @throws InvalidTypeException
Expand All @@ -484,8 +496,7 @@ protected function completeValue(
array $path,
&$result
) {
if ($this->isPromise($result)) {
/** @var ExtendedPromiseInterface $result */
if ($result instanceof ExtendedPromiseInterface) {
return $result->then(function (&$value) use ($returnType, $fieldNodes, $info, $path) {
return $this->completeValue($returnType, $fieldNodes, $info, $path, $value);
});
Expand Down Expand Up @@ -552,6 +563,7 @@ protected function completeValue(
* @param ResolveInfo $info
* @param array $path
* @param mixed $result
*
* @return array|PromiseInterface
* @throws ExecutionException
* @throws InvalidTypeException
Expand All @@ -572,8 +584,7 @@ protected function completeAbstractValue(
$runtimeType = $this->defaultTypeResolver($result, $this->context->getContextValue(), $info, $returnType);
}

if ($this->isPromise($runtimeType)) {
/** @var ExtendedPromiseInterface $runtimeType */
if ($runtimeType instanceof ExtendedPromiseInterface) {
return $runtimeType->then(function ($resolvedRuntimeType) use (
$returnType,
$fieldNodes,
Expand Down Expand Up @@ -605,6 +616,7 @@ protected function completeAbstractValue(
* @param NamedTypeInterface $returnType
* @param ResolveInfo $info
* @param mixed $result
*
* @return TypeInterface|ObjectType|null
* @throws ExecutionException
* @throws InvariantException
Expand Down Expand Up @@ -666,6 +678,7 @@ protected function ensureValidRuntimeType(
* @param mixed $context
* @param ResolveInfo $info
* @param AbstractTypeInterface $abstractType
*
* @return NamedTypeInterface|mixed|null
* @throws InvariantException
*/
Expand All @@ -686,7 +699,7 @@ protected function defaultTypeResolver(
foreach ($possibleTypes as $index => $type) {
$isTypeOfResult = $type->isTypeOf($value, $context, $info);

if ($this->isPromise($isTypeOfResult)) {
if ($isTypeOfResult instanceof ExtendedPromiseInterface) {
$promisedIsTypeOfResults[$index] = $isTypeOfResult;
continue;
}
Expand All @@ -713,6 +726,7 @@ protected function defaultTypeResolver(
return $possibleTypes[$index];
}
}

return null;
});
}
Expand All @@ -726,6 +740,7 @@ protected function defaultTypeResolver(
* @param ResolveInfo $info
* @param array $path
* @param mixed $result
*
* @return array|\React\Promise\Promise
* @throws \Throwable
*/
Expand Down Expand Up @@ -757,7 +772,7 @@ protected function completeListValue(
$fieldPath[] = $key;
$completedItem = $this->completeValueCatchingError($itemType, $fieldNodes, $info, $fieldPath, $item);
$completedItems[] = $completedItem;
$doesContainPromise = $doesContainPromise || $this->isPromise($completedItem);
$doesContainPromise = $doesContainPromise || $completedItem instanceof ExtendedPromiseInterface;
}

return $doesContainPromise
Expand All @@ -768,6 +783,7 @@ protected function completeListValue(
/**
* @param LeafTypeInterface|SerializableTypeInterface $returnType
* @param mixed $result
*
* @return mixed
* @throws ExecutionException
*/
Expand All @@ -791,6 +807,7 @@ protected function completeLeafValue($returnType, &$result)
* @param ResolveInfo $info
* @param array $path
* @param mixed $result
*
* @return array
* @throws ExecutionException
* @throws InvalidTypeException
Expand Down Expand Up @@ -825,6 +842,7 @@ protected function completeObjectValue(
* @param mixed $rootValue
* @param ExecutionContext $context
* @param ResolveInfo $info
*
* @return array|\Throwable
*/
protected function resolveFieldValueOrError(
Expand Down Expand Up @@ -854,11 +872,12 @@ protected function resolveFieldValueOrError(
* @param FieldNode[] $fieldNodes
* @param array $path
* @param mixed $result
*
* @return array
* @throws ExecutionException
* @throws InvalidTypeException
* @throws InvariantException
* @throws Throwable
* @throws \Throwable
*/
protected function executeSubFields(
ObjectType $returnType,
Expand Down Expand Up @@ -887,19 +906,11 @@ protected function executeSubFields(
return $result;
}

/**
* @param mixed $value
* @return bool
*/
protected function isPromise($value): bool
{
return $value instanceof ExtendedPromiseInterface;
}

/**
* @param \Throwable $originalException
* @param array $nodes
* @param array $path
*
* @return ExecutionException
*/
protected function buildLocatedError(
Expand Down Expand Up @@ -933,6 +944,7 @@ protected function buildLocatedError(
* @param ObjectType $parentType
* @param array|null $path
* @param ExecutionContext $context
*
* @return ResolveInfo
*/
protected function createResolveInfo(
Expand Down Expand Up @@ -976,6 +988,7 @@ protected function handleError(ExecutionException $error)
* @param array $arguments
* @param mixed $contextValues
* @param ResolveInfo $info
*
* @return mixed|null
*/
public static function defaultFieldResolver($rootValue, array $arguments, $contextValues, ResolveInfo $info)
Expand Down

0 comments on commit 9fbb8cd

Please sign in to comment.