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 DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public function getConfigTreeBuilder()
->end()
->end()
->end()
->booleanNode('map_exceptions_to_parent')->defaultFalse()->end()
->arrayNode('exceptions')
->addDefaultsIfNotSet()
->children()
Expand Down
7 changes: 7 additions & 0 deletions DependencyInjection/OverblogGraphQLExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,13 @@ private function setErrorHandlerArguments(array $config, ContainerBuilder $conta
$errorHandlerDefinition->replaceArgument(0, $config['definitions']['internal_error_message']);
}

if (isset($config['definitions']['map_exceptions_to_parent'])) {
$errorHandlerDefinition->replaceArgument(
3,
$config['definitions']['map_exceptions_to_parent']
);
}

if (isset($config['definitions']['exceptions'])) {
$errorHandlerDefinition
->replaceArgument(2, $this->buildExceptionMap($config['definitions']['exceptions']))
Expand Down
51 changes: 46 additions & 5 deletions Error/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,22 @@ class ErrorHandler
/** @var string */
private $userErrorClass = self::DEFAULT_USER_ERROR_CLASS;

public function __construct($internalErrorMessage = null, LoggerInterface $logger = null, array $exceptionMap = [])
{
/** @var bool */
private $mapExceptionsToParent;

public function __construct(
$internalErrorMessage = null,
LoggerInterface $logger = null,
array $exceptionMap = [],
$mapExceptionsToParent = false
) {
$this->logger = (null === $logger) ? new NullLogger() : $logger;
if (empty($internalErrorMessage)) {
$internalErrorMessage = self::DEFAULT_ERROR_MESSAGE;
}
$this->internalErrorMessage = $internalErrorMessage;
$this->exceptionMap = $exceptionMap;
$this->mapExceptionsToParent = $mapExceptionsToParent;
}

public function setUserWarningClass($userWarningClass)
Expand Down Expand Up @@ -170,13 +178,46 @@ protected function convertException($rawException = null)
return;
}

$errorClass = $this->findErrorClass($rawException);
if (null !== $errorClass) {
return new $errorClass($rawException->getMessage(), $rawException->getCode(), $rawException);
}

return $rawException;
}

/**
* @param \Exception|\Error $rawException
*
* @return string|null
*/
private function findErrorClass($rawException)
{
$rawExceptionClass = get_class($rawException);
if (isset($this->exceptionMap[$rawExceptionClass])) {
$errorClass = $this->exceptionMap[$rawExceptionClass];
return $this->exceptionMap[$rawExceptionClass];
}

return new $errorClass($rawException->getMessage(), $rawException->getCode(), $rawException);
if ($this->mapExceptionsToParent) {
return $this->findErrorClassUsingParentException($rawException);
}

return $rawException;
return null;
}

/**
* @param \Exception|\Error $rawException
*
* @return string|null
*/
private function findErrorClassUsingParentException($rawException)
{
foreach ($this->exceptionMap as $rawExceptionClass => $errorClass) {
if ($rawException instanceof $rawExceptionClass) {
return $errorClass;
}
}

return null;
}
}
1 change: 1 addition & 0 deletions Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ services:
- ~
- "@?logger"
- []
- false

overblog_graphql.executor.default:
class: Overblog\GraphQLBundle\Executor\Executor
Expand Down
3 changes: 3 additions & 0 deletions Resources/doc/security/errors-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ overblog_graphql:
#...
definitions:
#...
# change to true to try to map an exception to a parent exception if the exact exception is not in
# the mapping
map_exceptions_to_parent: false
exceptions:
warnings:
- "Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException"
Expand Down
9 changes: 9 additions & 0 deletions Tests/Error/ChildOfInvalidArgumentException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Overblog\GraphQLBundle\Tests\Error;

use InvalidArgumentException;

final class ChildOfInvalidArgumentException extends InvalidArgumentException
{
}
136 changes: 136 additions & 0 deletions Tests/Error/ErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,140 @@ public function testConvertExceptionToUserWarning()

$this->assertEquals($expected, $executionResult->toArray());
}

/**
* @param array $exceptionMap
* @param bool $mapExceptionsToParent
* @param array|string $expectedUserError
*
* @dataProvider parentExceptionMappingDataProvider
*/
public function testConvertExceptionUsingParentExceptionMatchesAlwaysFirstExactExceptionOtherwiseMatchesParent(array $exceptionMap, $mapExceptionsToParent, $expectedUserError)
{
$errorHandler = new ErrorHandler(null, null, $exceptionMap, $mapExceptionsToParent);

$executionResult = new ExecutionResult(
null,
[
new GraphQLError(
'Error with invalid argument exception',
null,
null,
null,
null,
new ChildOfInvalidArgumentException('Invalid argument exception')
),
]
);

if (is_string($expectedUserError)) {
self::expectException($expectedUserError);
}
$errorHandler->handleErrors($executionResult, true);

if (is_array($expectedUserError)) {
$this->assertEquals($expectedUserError, $executionResult->toArray());
}
}

/**
* @return array
*/
public function parentExceptionMappingDataProvider()
{
return [
'without $mapExceptionsToParent and only the exact class, maps to exact class' => [
[
ChildOfInvalidArgumentException::class => UserError::class,
],
false,
[
'errors' => [
[
'message' => 'Error with invalid argument exception',
],
],
],
],
'without $mapExceptionsToParent and only the parent class, does not map to parent' => [
[
\InvalidArgumentException::class => UserWarning::class,
],
false,
ChildOfInvalidArgumentException::class,
],
'with $mapExceptionsToParent and only the exact class' => [
[
ChildOfInvalidArgumentException::class => UserError::class,
],
true,
[
'errors' => [
[
'message' => 'Error with invalid argument exception',
],
],
],
],
'with $mapExceptionsToParent and only the parent class' => [
[
\InvalidArgumentException::class => UserWarning::class,
],
true,
[
'extensions' => [
'warnings' => [
[
'message' => 'Error with invalid argument exception',
],
],
],
],
],
'with $mapExceptionsToParent and the exact class first matches exact class' => [
[
ChildOfInvalidArgumentException::class => UserError::class,
\InvalidArgumentException::class => UserWarning::class,
],
true,
[
'errors' => [
[
'message' => 'Error with invalid argument exception',
],
],
],
],
'with $mapExceptionsToParent and the exact class first but parent maps to error' => [
[
ChildOfInvalidArgumentException::class => UserWarning::class,
\InvalidArgumentException::class => UserError::class,
],
true,
[
'extensions' => [
'warnings' => [
[
'message' => 'Error with invalid argument exception',
],
],
],
],
],
'with $mapExceptionsToParent and the parent class first still matches exact class' => [
[
\InvalidArgumentException::class => UserWarning::class,
ChildOfInvalidArgumentException::class => UserError::class,
],
true,
[
'errors' => [
[
'message' => 'Error with invalid argument exception',
],
],
],
],
];
}
}