Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/Exceptions/GraphQLAggregateException.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
use Exception;
use GraphQL\Error\ClientAware;
use Throwable;
use function array_map;
use function count;
use function max;
use function reset;

class GraphQLAggregateException extends Exception implements GraphQLAggregateExceptionInterface
{
Expand All @@ -31,6 +35,7 @@ public function add(ClientAware $exception): void
{
$this->exceptions[] = $exception;
$this->message .= "\n" . $exception->getMessage();
$this->updateCode();
}

/**
Expand All @@ -45,4 +50,36 @@ public function hasExceptions(): bool
{
return ! empty($this->exceptions);
}

/**
* By convention, the aggregated code is the highest code of all exceptions
*/
private function updateCode(): void
{
$codes = array_map(static function (Throwable $t) {
return $t->getCode();
}, $this->exceptions);
$this->code = max($codes);
}

/**
* Throw the exceptions passed in parameter.
* If only one exception is passed, it is thrown.
* If many exceptions are passed, they are bundled in the GraphQLAggregateException
*
* @param (ClientAware&Throwable)[] $exceptions
*/
public static function throwExceptions(array $exceptions): void
{
$count = count($exceptions);
if ($count === 0) {
return;
}
if ($count === 1) {
/** @var Throwable $exception */
$exception = reset($exceptions);
throw $exception;
}
throw new self($exceptions);
}
}
31 changes: 30 additions & 1 deletion src/Parameters/MissingArgumentException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
namespace TheCodingMachine\GraphQLite\Parameters;

use BadMethodCallException;
use TheCodingMachine\GraphQLite\Exceptions\GraphQLExceptionInterface;
use function get_class;
use function is_array;
use function is_string;
use function sprintf;

class MissingArgumentException extends BadMethodCallException
class MissingArgumentException extends BadMethodCallException implements GraphQLExceptionInterface
{
public static function create(string $argumentName): self
{
Expand Down Expand Up @@ -66,4 +67,32 @@ private static function toMethod(callable $callable): string

return $factoryName . '::' . $callable[1] . '()';
}

/**
* Returns true when exception message is safe to be displayed to a client.
*/
public function isClientSafe(): bool
{
return true;
}

/**
* Returns string describing a category of the error.
*
* Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.
*/
public function getCategory(): string
{
return 'graphql';
}

/**
* Returns the "extensions" object attached to the GraphQL error.
*
* @return array<string, mixed>
*/
public function getExtensions(): array
{
return [];
}
}
17 changes: 12 additions & 5 deletions src/QueryField.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
namespace TheCodingMachine\GraphQLite;

use GraphQL\Deferred;
use GraphQL\Error\ClientAware;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\OutputType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use InvalidArgumentException;
use TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException;
use TheCodingMachine\GraphQLite\Middlewares\MissingAuthorizationException;
use TheCodingMachine\GraphQLite\Parameters\MissingArgumentException;
use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;
use TheCodingMachine\GraphQLite\Parameters\PrefetchDataParameter;
use TheCodingMachine\GraphQLite\Parameters\SourceParameter;
use Webmozart\Assert\Assert;
use function array_map;
use function array_unshift;
use function array_values;
use function get_class;
use function is_object;

Expand Down Expand Up @@ -237,12 +237,19 @@ public static function externalField(QueryFieldDescriptor $fieldDescriptor): sel
*/
private function paramsToArguments(array $parameters, ?object $source, array $args, $context, ResolveInfo $info, callable $resolve): array
{
return array_values(array_map(function (ParameterInterface $parameter) use ($source, $args, $context, $info, $resolve) {
$toPassArgs = [];
$exceptions = [];
foreach ($parameters as $parameter) {
try {
return $parameter->resolve($source, $args, $context, $info);
$toPassArgs[] = $parameter->resolve($source, $args, $context, $info);
} catch (MissingArgumentException $e) {
throw MissingArgumentException::wrapWithFieldContext($e, $this->name, $resolve);
} catch (ClientAware $e) {
$exceptions[] = $e;
}
}, $parameters));
}
GraphQLAggregateException::throwExceptions($exceptions);

return $toPassArgs;
}
}
9 changes: 9 additions & 0 deletions src/Types/ResolvableMutableInputObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace TheCodingMachine\GraphQLite\Types;

use GraphQL\Error\ClientAware;
use GraphQL\Type\Definition\ResolveInfo;
use ReflectionMethod;
use TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException;
use TheCodingMachine\GraphQLite\FieldsBuilder;
use TheCodingMachine\GraphQLite\InputTypeGenerator;
use TheCodingMachine\GraphQLite\InputTypeUtils;
Expand Down Expand Up @@ -102,13 +104,17 @@ public function resolve(?object $source, array $args, $context, ResolveInfo $res
$parameters = $this->getParameters();

$toPassArgs = [];
$exceptions = [];
foreach ($parameters as $parameter) {
try {
$toPassArgs[] = $parameter->resolve($source, $args, $context, $resolveInfo);
} catch (MissingArgumentException $e) {
throw MissingArgumentException::wrapWithFactoryContext($e, $this->name, $this->resolve);
} catch (ClientAware $e) {
$exceptions[] = $e;
}
}
GraphQLAggregateException::throwExceptions($exceptions);

$resolve = $this->resolve;

Expand All @@ -123,8 +129,11 @@ public function resolve(?object $source, array $args, $context, ResolveInfo $res
$toPassArgs[] = $parameter->resolve($source, $args, $context, $resolveInfo);
} catch (MissingArgumentException $e) {
throw MissingArgumentException::wrapWithDecoratorContext($e, $this->name, $decorator);
} catch (ClientAware $e) {
$exceptions[] = $e;
}
}
GraphQLAggregateException::throwExceptions($exceptions);

$object = $decorator(...$toPassArgs);
}
Expand Down
18 changes: 15 additions & 3 deletions tests/Exceptions/GraphQLAggregateExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,23 @@ class GraphQLAggregateExceptionTest extends TestCase

public function testAggregateException()
{
$error = new Error('foo');
$error = new GraphQLException('foo', 12);
$error2 = new GraphQLException('bar', 42);
$exceptions = new GraphQLAggregateException([$error]);
$exceptions->add($error);
$exceptions->add($error2);

$this->assertSame([$error, $error], $exceptions->getExceptions());
$this->assertSame([$error, $error2], $exceptions->getExceptions());
$this->assertSame(42, $exceptions->getCode());
$this->assertTrue($exceptions->hasExceptions());
}

public function testOnlyOneAggregateExceptionThrowsTheSameException()
{
$error = new GraphQLException('foo', 12);

$this->expectException(GraphQLException::class);
GraphQLAggregateException::throwExceptions([$error]);

}

}
27 changes: 27 additions & 0 deletions tests/Mappers/Parameters/HardCodedParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php


namespace TheCodingMachine\GraphQLite\Mappers\Parameters;


use GraphQL\Type\Definition\ResolveInfo;
use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;

class HardCodedParameter implements ParameterInterface
{
private $value;

/**
* @param mixed $value
*/
public function __construct($value)
{

$this->value = $value;
}

public function resolve(?object $source, array $args, $context, ResolveInfo $info)
{
return $this->value;
}
}
4 changes: 4 additions & 0 deletions tests/Parameters/MissingArgumentExceptionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,9 @@ public function testWrapWithFactoryContext(): void
$e3 = MissingArgumentException::wrapWithFactoryContext($e, 'Input', function() {});

$this->assertEquals('Expected argument \'foo\' was not provided in GraphQL input type \'Input\' used in factory \'\'', $e3->getMessage());

$this->assertTrue($e->isClientSafe());
$this->assertSame([], $e->getExtensions());
$this->assertSame('graphql', $e->getCategory());
}
}
30 changes: 30 additions & 0 deletions tests/QueryFieldTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace TheCodingMachine\GraphQLite;

use GraphQL\Error\Error;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use PHPUnit\Framework\TestCase;
use stdClass;
use TheCodingMachine\GraphQLite\Fixtures\TestObject;
use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;

class QueryFieldTest extends TestCase
{

public function testExceptionsHandling()
{
$queryField = new QueryField('foo', Type::string(), [ new class implements ParameterInterface {
public function resolve(?object $source, array $args, $context, ResolveInfo $info)
{
throw new Error('boum');
}
} ], null, 'getTest', null, null, []);

$resolve = $queryField->resolveFn;

$this->expectException(Error::class);
$resolve(new TestObject('foo'), ['arg' => 12], null, $this->createMock(ResolveInfo::class));
}
}
Loading