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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* GraphQL: Subscription support with Mercure (#3321)
* GraphQL: Allow to format GraphQL errors based on exceptions (#3063)
* GraphQL: Add page-based pagination (#3175, #3517)
* GraphQL: Errors thrown from the GraphQL library can now be handled (#3632)
* GraphQL: Possibility to add a custom description for queries, mutations and subscriptions (#3477, #3514)
* GraphQL: Support for field name conversion (serialized name) (#3455, #3516)
* GraphQL: **BC** `operation` is now `operationName` to follow the standard (#3568)
Expand Down
4 changes: 1 addition & 3 deletions features/bootstrap/DoctrineContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\NetworkPathDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\NetworkPathRelationDummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Node;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Order;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Person;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\PersonToPet;
Expand Down Expand Up @@ -1542,7 +1541,7 @@ public function thereAreDummyMercureObjects(int $nb)
{
for ($i = 1; $i <= $nb; ++$i) {
$relatedDummy = $this->buildRelatedDummy();
$relatedDummy->setName('RelatedDummy #' . $i);
$relatedDummy->setName('RelatedDummy #'.$i);

$dummyMercure = $this->buildDummyMercure();
$dummyMercure->name = "Dummy Mercure #$i";
Expand Down Expand Up @@ -1974,7 +1973,6 @@ private function buildConvertedRelated()
private function buildDummyMercure()
{
return $this->isOrm() ? new DummyMercure() : new DummyMercureDocument();

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface;
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\GraphQl\Error\ErrorHandlerInterface;
use ApiPlatform\Core\GraphQl\Resolver\MutationResolverInterface;
use ApiPlatform\Core\GraphQl\Resolver\QueryCollectionResolverInterface;
use ApiPlatform\Core\GraphQl\Resolver\QueryItemResolverInterface;
Expand Down Expand Up @@ -463,6 +464,8 @@ private function registerGraphQlConfiguration(ContainerBuilder $container, array
->addTag('api_platform.graphql.mutation_resolver');
$container->registerForAutoconfiguration(GraphQlTypeInterface::class)
->addTag('api_platform.graphql.type');
$container->registerForAutoconfiguration(ErrorHandlerInterface::class)
->addTag('api_platform.graphql.error_handler');
}

private function registerLegacyBundlesConfiguration(ContainerBuilder $container, array $config, XmlFileLoader $loader): void
Expand Down
5 changes: 5 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/graphql.xml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@
<argument type="service" id="api_platform.graphql.action.graphiql" />
<argument type="service" id="api_platform.graphql.action.graphql_playground" />
<argument type="service" id="serializer" />
<argument type="service" id="api_platform.graphql.error_handler" />
<argument>%kernel.debug%</argument>
<argument>%api_platform.graphql.graphiql.enabled%</argument>
<argument>%api_platform.graphql.graphql_playground.enabled%</argument>
Expand All @@ -199,6 +200,10 @@
<argument>%api_platform.title%</argument>
</service>

<!-- Error -->

<service id="api_platform.graphql.error_handler" class="ApiPlatform\Core\GraphQl\Error\ErrorHandler" public="false" />

<!-- Serializer -->

<service id="api_platform.graphql.normalizer.item" class="ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer" public="false">
Expand Down
7 changes: 6 additions & 1 deletion src/GraphQl/Action/EntrypointAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace ApiPlatform\Core\GraphQl\Action;

use ApiPlatform\Core\GraphQl\Error\ErrorHandlerInterface;
use ApiPlatform\Core\GraphQl\ExecutorInterface;
use ApiPlatform\Core\GraphQl\Type\SchemaBuilderInterface;
use GraphQL\Error\Debug;
Expand All @@ -38,18 +39,20 @@ final class EntrypointAction
private $graphiQlAction;
private $graphQlPlaygroundAction;
private $normalizer;
private $errorHandler;
private $debug;
private $graphiqlEnabled;
private $graphQlPlaygroundEnabled;
private $defaultIde;

public function __construct(SchemaBuilderInterface $schemaBuilder, ExecutorInterface $executor, GraphiQlAction $graphiQlAction, GraphQlPlaygroundAction $graphQlPlaygroundAction, NormalizerInterface $normalizer, bool $debug = false, bool $graphiqlEnabled = false, bool $graphQlPlaygroundEnabled = false, $defaultIde = false)
public function __construct(SchemaBuilderInterface $schemaBuilder, ExecutorInterface $executor, GraphiQlAction $graphiQlAction, GraphQlPlaygroundAction $graphQlPlaygroundAction, NormalizerInterface $normalizer, ErrorHandlerInterface $errorHandler, bool $debug = false, bool $graphiqlEnabled = false, bool $graphQlPlaygroundEnabled = false, $defaultIde = false)
{
$this->schemaBuilder = $schemaBuilder;
$this->executor = $executor;
$this->graphiQlAction = $graphiQlAction;
$this->graphQlPlaygroundAction = $graphQlPlaygroundAction;
$this->normalizer = $normalizer;
$this->errorHandler = $errorHandler;
$this->debug = $debug ? Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE : false;
$this->graphiqlEnabled = $graphiqlEnabled;
$this->graphQlPlaygroundEnabled = $graphQlPlaygroundEnabled;
Expand All @@ -76,9 +79,11 @@ public function __invoke(Request $request): Response

$executionResult = $this->executor
->executeQuery($this->schemaBuilder->getSchema(), $query, null, null, $variables, $operationName)
->setErrorsHandler($this->errorHandler)
->setErrorFormatter([$this->normalizer, 'normalize']);
} catch (\Exception $exception) {
$executionResult = (new ExecutionResult(null, [new Error($exception->getMessage(), null, null, null, null, $exception)]))
->setErrorsHandler($this->errorHandler)
->setErrorFormatter([$this->normalizer, 'normalize']);
}

Expand Down
32 changes: 32 additions & 0 deletions src/GraphQl/Error/ErrorHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\GraphQl\Error;

/**
* Handles the errors thrown by the GraphQL library by applying the formatter to them (default behavior).
*
* @experimental
*
* @author Ollie Harridge <[email protected]>
*/
class ErrorHandler implements ErrorHandlerInterface
{
/**
* {@inheritdoc}
*/
public function __invoke(array $errors, callable $formatter): array
{
return array_map($formatter, $errors);
}
}
32 changes: 32 additions & 0 deletions src/GraphQl/Error/ErrorHandlerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Core\GraphQl\Error;

use GraphQL\Error\Error;

/**
* Handles the errors thrown by the GraphQL library.
* It is responsible for applying the formatter to the errors and can be used for filtering or logging them.
*
* @experimental
*
* @author Ollie Harridge <[email protected]>
*/
interface ErrorHandlerInterface
{
/**
* @param Error[] $errors
*/
public function __invoke(array $errors, callable $formatter): array;
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\Exception\FilterValidationException;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use ApiPlatform\Core\GraphQl\Error\ErrorHandlerInterface;
use ApiPlatform\Core\GraphQl\Resolver\MutationResolverInterface;
use ApiPlatform\Core\GraphQl\Resolver\QueryCollectionResolverInterface;
use ApiPlatform\Core\GraphQl\Resolver\QueryItemResolverInterface;
Expand Down Expand Up @@ -342,6 +343,7 @@ public function testDisableGraphQl()
$containerBuilderProphecy->setDefinition('api_platform.graphql.action.entrypoint', Argument::type(Definition::class))->shouldNotBeCalled();
$containerBuilderProphecy->setDefinition('api_platform.graphql.action.graphiql', Argument::type(Definition::class))->shouldNotBeCalled();
$containerBuilderProphecy->setDefinition('api_platform.graphql.action.graphql_playground', Argument::type(Definition::class))->shouldNotBeCalled();
$containerBuilderProphecy->setDefinition('api_platform.graphql.error_handler', Argument::type(Definition::class))->shouldNotBeCalled();
$containerBuilderProphecy->setDefinition('api_platform.graphql.resolver.factory.collection', Argument::type(Definition::class))->shouldNotBeCalled();
$containerBuilderProphecy->setDefinition('api_platform.graphql.resolver.factory.item_mutation', Argument::type(Definition::class))->shouldNotBeCalled();
$containerBuilderProphecy->setDefinition('api_platform.graphql.resolver.factory.item_subscription', Argument::type(Definition::class))->shouldNotBeCalled();
Expand Down Expand Up @@ -392,6 +394,8 @@ public function testDisableGraphQl()
$containerBuilderProphecy->setParameter('api_platform.graphql.graphql_playground.enabled', false)->shouldBeCalled();
$containerBuilderProphecy->registerForAutoconfiguration(GraphQlTypeInterface::class)->shouldNotBeCalled();
$this->childDefinitionProphecy->addTag('api_platform.graphql.type')->shouldNotBeCalled();
$containerBuilderProphecy->registerForAutoconfiguration(ErrorHandlerInterface::class)->shouldNotBeCalled();
$this->childDefinitionProphecy->addTag('api_platform.graphql.error_handler')->shouldNotBeCalled();
$containerBuilderProphecy->registerForAutoconfiguration(QueryItemResolverInterface::class)->shouldNotBeCalled();
$containerBuilderProphecy->registerForAutoconfiguration(QueryCollectionResolverInterface::class)->shouldNotBeCalled();
$this->childDefinitionProphecy->addTag('api_platform.graphql.query_resolver')->shouldNotBeCalled();
Expand Down Expand Up @@ -1077,6 +1081,10 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo
->willReturn($this->childDefinitionProphecy)->shouldBeCalledTimes(1);
$this->childDefinitionProphecy->addTag('api_platform.graphql.type')->shouldBeCalledTimes(1);

$containerBuilderProphecy->registerForAutoconfiguration(ErrorHandlerInterface::class)
->willReturn($this->childDefinitionProphecy)->shouldBeCalledTimes(1);
$this->childDefinitionProphecy->addTag('api_platform.graphql.error_handler')->shouldBeCalledTimes(1);

$containerBuilderProphecy->registerForAutoconfiguration(QueryItemResolverInterface::class)
->willReturn($this->childDefinitionProphecy)->shouldBeCalledTimes(1);
$containerBuilderProphecy->registerForAutoconfiguration(QueryCollectionResolverInterface::class)
Expand Down Expand Up @@ -1195,6 +1203,7 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo
'api_platform.graphql.action.entrypoint',
'api_platform.graphql.action.graphiql',
'api_platform.graphql.action.graphql_playground',
'api_platform.graphql.error_handler',
'api_platform.graphql.executor',
'api_platform.graphql.type_builder',
'api_platform.graphql.fields_builder',
Expand Down
5 changes: 4 additions & 1 deletion tests/GraphQl/Action/EntrypointActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Core\GraphQl\Action\EntrypointAction;
use ApiPlatform\Core\GraphQl\Action\GraphiQlAction;
use ApiPlatform\Core\GraphQl\Action\GraphQlPlaygroundAction;
use ApiPlatform\Core\GraphQl\Error\ErrorHandler;
use ApiPlatform\Core\GraphQl\ExecutorInterface;
use ApiPlatform\Core\GraphQl\Serializer\Exception\ErrorNormalizer;
use ApiPlatform\Core\GraphQl\Serializer\Exception\HttpExceptionNormalizer;
Expand Down Expand Up @@ -235,10 +236,12 @@ private function getEntrypointAction(array $variables = ['graphqlVariable']): En
new HttpExceptionNormalizer(),
new ErrorNormalizer(),
]);
$errorHandler = new ErrorHandler();

$executionResultProphecy = $this->prophesize(ExecutionResult::class);
$executionResultProphecy->toArray(false)->willReturn(['GraphQL']);
$executionResultProphecy->setErrorFormatter([$normalizer, 'normalize'])->willReturn($executionResultProphecy);
$executionResultProphecy->setErrorsHandler($errorHandler)->willReturn($executionResultProphecy);
$executorProphecy = $this->prophesize(ExecutorInterface::class);
$executorProphecy->executeQuery(Argument::is($schema->reveal()), 'graphqlQuery', null, null, $variables, 'graphqlOperationName')->willReturn($executionResultProphecy->reveal());

Expand All @@ -248,6 +251,6 @@ private function getEntrypointAction(array $variables = ['graphqlVariable']): En
$graphiQlAction = new GraphiQlAction($twigProphecy->reveal(), $routerProphecy->reveal(), true);
$graphQlPlaygroundAction = new GraphQlPlaygroundAction($twigProphecy->reveal(), $routerProphecy->reveal(), true);

return new EntrypointAction($schemaBuilderProphecy->reveal(), $executorProphecy->reveal(), $graphiQlAction, $graphQlPlaygroundAction, $normalizer, false, true, true, 'graphiql');
return new EntrypointAction($schemaBuilderProphecy->reveal(), $executorProphecy->reveal(), $graphiQlAction, $graphQlPlaygroundAction, $normalizer, $errorHandler, false, true, true, 'graphiql');
}
}