Skip to content

Commit 0ef2a6b

Browse files
authored
Merge pull request #64 from adri/feature/exception-mapping
Exceptions can be mapped to UserError and UserWarning
2 parents ece07fe + 0c9dab1 commit 0ef2a6b

File tree

12 files changed

+275
-2
lines changed

12 files changed

+275
-2
lines changed

DependencyInjection/Configuration.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ public function getConfigTreeBuilder()
6666
->end()
6767
->end()
6868
->end()
69+
->arrayNode('exceptions')
70+
->children()
71+
->arrayNode('warnings')
72+
->treatNullLike(array())
73+
->prototype('scalar')->end()
74+
->end()
75+
->arrayNode('errors')
76+
->treatNullLike(array())
77+
->prototype('scalar')->end()
78+
->end()
79+
->arrayNode('types')
80+
->addDefaultsIfNotSet()
81+
->children()
82+
->scalarNode('warnings')
83+
->defaultValue('Overblog\\GraphQLBundle\\Error\\UserWarning')
84+
->end()
85+
->scalarNode('errors')
86+
->defaultValue('Overblog\\GraphQLBundle\\Error\\UserError')
87+
->end()
88+
->end()
89+
->end()
90+
->end()
91+
->end()
92+
6993
->arrayNode('builders')
7094
->children()
7195
->arrayNode('field')

DependencyInjection/OverblogGraphQLExtension.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ private function setErrorHandlerArguments(array $config, ContainerBuilder $conta
102102
->replaceArgument(0, $config['definitions']['internal_error_message'])
103103
;
104104
}
105+
106+
if (isset($config['definitions']['exceptions'])) {
107+
$container
108+
->getDefinition($this->getAlias().'.error_handler')
109+
->replaceArgument(2, $this->buildExceptionMap($config['definitions']['exceptions']))
110+
;
111+
}
105112
}
106113

107114
private function setSchemaBuilderArguments(array $config, ContainerBuilder $container)
@@ -142,4 +149,28 @@ public function getConfiguration(array $config, ContainerBuilder $container)
142149
{
143150
return new Configuration($container->getParameter('kernel.debug'));
144151
}
152+
153+
/**
154+
* Returns a list of custom exceptions mapped to error/warning classes.
155+
*
156+
* @param array $config
157+
* @return array Custom exception map, [exception => UserError/UserWarning].
158+
*/
159+
private function buildExceptionMap(array $exceptionConfig)
160+
{
161+
$exceptionMap = [];
162+
$typeMap = $exceptionConfig['types'];
163+
164+
foreach ($exceptionConfig as $type => $exceptionList) {
165+
if ('types' === $type) {
166+
continue;
167+
}
168+
169+
foreach ($exceptionList as $exception) {
170+
$exceptionMap[$exception] = $typeMap[$type];
171+
}
172+
}
173+
174+
return $exceptionMap;
175+
}
145176
}

Error/ErrorHandler.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ class ErrorHandler
2626
/** @var string */
2727
private $internalErrorMessage;
2828

29-
public function __construct($internalErrorMessage = null, LoggerInterface $logger = null)
29+
/** @var array */
30+
private $exceptionMap;
31+
32+
public function __construct($internalErrorMessage = null, LoggerInterface $logger = null, array $exceptionMap = [])
3033
{
3134
$this->logger = (null === $logger) ? new NullLogger() : $logger;
3235
if (empty($internalErrorMessage)) {
3336
$internalErrorMessage = self::DEFAULT_ERROR_MESSAGE;
3437
}
3538
$this->internalErrorMessage = $internalErrorMessage;
39+
$this->exceptionMap = $exceptionMap;
3640
}
3741

3842
/**
@@ -54,7 +58,7 @@ protected function treatExceptions(array $errors, $throwRawException)
5458

5559
/** @var Error $error */
5660
foreach ($errors as $error) {
57-
$rawException = $error->getPrevious();
61+
$rawException = $this->convertException($error->getPrevious());
5862

5963
// Parse error or user error
6064
if (null === $rawException) {
@@ -129,4 +133,26 @@ public function handleErrors(ExecutionResult $executionResult, $throwRawExceptio
129133
$executionResult->extensions['warnings'] = array_map(['GraphQL\Error', 'formatError'], $exceptions['extensions']['warnings']);
130134
}
131135
}
136+
137+
/**
138+
* Tries to convert a raw exception into a user warning or error
139+
* that is displayed to the user.
140+
*
141+
* @param \Exception $rawException
142+
* @return \Exception
143+
*/
144+
protected function convertException(\Exception $rawException = null)
145+
{
146+
if (null === $rawException) {
147+
return null;
148+
}
149+
150+
if (!empty($this->exceptionMap[get_class($rawException)])) {
151+
$errorClass = $this->exceptionMap[get_class($rawException)];
152+
153+
return new $errorClass($rawException->getMessage(), $rawException->getCode(), $rawException);
154+
}
155+
156+
return $rawException;
157+
}
132158
}

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,25 @@ class CharacterResolver
665665
}
666666
```
667667

668+
If you want to map your own exceptions to warnings and errors you can
669+
define a custom exception mapping:
670+
671+
```yaml
672+
#app/config/config.yml
673+
overblog_graphql:
674+
#...
675+
definitions:
676+
#...
677+
exceptions:
678+
warnings:
679+
- "Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException"
680+
errors:
681+
- "InvalidArgumentException"
682+
```
683+
684+
The message of those exceptions are then shown to the user like other
685+
`UserError`s or `UserWarning`s.
686+
668687
Security
669688
--------
670689

Resources/config/services.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ services:
99
arguments:
1010
- ~
1111
- "@?logger"
12+
- []
1213

1314
overblog_graphql.request_executor:
1415
class: Overblog\GraphQLBundle\Request\Executor

Tests/DependencyInjection/OverblogGraphQLTypesExtensionTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,42 @@ public function testInternalConfigKeysShouldNotBeUsed($internalConfigKey)
8282
$this->extension->load($configs, $this->container);
8383
}
8484

85+
/**
86+
* @runInSeparateProcess
87+
*/
88+
public function testCustomExceptions()
89+
{
90+
$ext = new OverblogGraphQLExtension();
91+
$ext->load(
92+
[
93+
[
94+
'definitions' => [
95+
'exceptions' => [
96+
'warnings' => [
97+
'Symfony\Component\Routing\Exception\ResourceNotFoundException'
98+
],
99+
'errors' => [
100+
'InvalidArgumentException'
101+
],
102+
]
103+
],
104+
],
105+
],
106+
$this->container
107+
);
108+
109+
$expectedExceptionMap = [
110+
'Symfony\Component\Routing\Exception\ResourceNotFoundException' => 'Overblog\\GraphQLBundle\\Error\\UserWarning',
111+
'InvalidArgumentException' => 'Overblog\\GraphQLBundle\\Error\\UserError',
112+
];
113+
114+
$definition = $this->container->getDefinition('overblog_graphql.error_handler');
115+
$this->assertEquals($expectedExceptionMap, $definition->getArgument(2));
116+
}
117+
118+
/**
119+
* @runInSeparateProcess
120+
*/
85121
public function testCustomBuilders()
86122
{
87123
$ext = new OverblogGraphQLExtension();

Tests/Error/ErrorHandlerTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,31 @@ public function testMaskErrorWithoutWrappedExceptionAndThrowExceptionSetToTrue()
138138

139139
$this->assertEquals($expected, $executionResult->toArray());
140140
}
141+
142+
public function testConvertExceptionToUserWarning()
143+
{
144+
$errorHandler = new ErrorHandler(null, null, ["InvalidArgumentException" => 'Overblog\\GraphQLBundle\\Error\\UserWarning']);
145+
146+
$executionResult = new ExecutionResult(
147+
null,
148+
[
149+
new Error('Error with invalid argument exception', null, new \InvalidArgumentException('Invalid argument exception')),
150+
]
151+
);
152+
153+
$errorHandler->handleErrors($executionResult, true);
154+
155+
$expected = [
156+
'data' => null,
157+
'extensions' => [
158+
'warnings' => [
159+
[
160+
'message' => 'Error with invalid argument exception',
161+
],
162+
],
163+
],
164+
];
165+
166+
$this->assertEquals($expected, $executionResult->toArray());
167+
}
141168
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the OverblogGraphQLBundle package.
5+
*
6+
* (c) Overblog <http://github.com/overblog/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Overblog\GraphQLBundle\Tests\Functional\Exception;
13+
14+
use Overblog\GraphQLBundle\Tests\Functional\TestCase;
15+
16+
/**
17+
* Class ConnectionTest.
18+
*
19+
* @see https://github.com/graphql/graphql-relay-js/blob/master/src/connection/__tests__/connection.js
20+
*/
21+
class ExceptionTest extends TestCase
22+
{
23+
protected function setUp()
24+
{
25+
parent::setUp();
26+
27+
static::createAndBootKernel(['test_case' => 'exception']);
28+
}
29+
30+
public function testExceptionIsMappedToAWarning()
31+
{
32+
$query = <<<EOF
33+
query ExceptionQuery {
34+
test
35+
}
36+
EOF;
37+
38+
$expectedData = [
39+
'test' => null,
40+
];
41+
42+
$expectedErrors = [
43+
[
44+
'message' => 'Invalid argument exception',
45+
'locations' => [
46+
[
47+
'line' => 2,
48+
'column' => 5,
49+
]
50+
],
51+
],
52+
];
53+
54+
$this->assertGraphQL($query, $expectedData, $expectedErrors);
55+
}
56+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the OverblogGraphQLBundle package.
5+
*
6+
* (c) Overblog <http://github.com/overblog/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Overblog\GraphQLBundle\Tests\Functional\app\Exception;
13+
14+
class ExampleException
15+
{
16+
public function throwException()
17+
{
18+
throw new \InvalidArgumentException('Invalid argument exception');
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
imports:
2+
- { resource: ../config.yml }
3+
- { resource: services.yml }
4+
5+
parameters:
6+
overblog_graphql.type_class_namespace: "Overblog\\GraphQLBundle\\Exception\\__DEFINITIONS__"
7+
8+
overblog_graphql:
9+
definitions:
10+
exceptions:
11+
errors:
12+
- "InvalidArgumentException"
13+
schema:
14+
query: Query
15+
mutation: ~
16+
mappings:
17+
types:
18+
-
19+
type: yml
20+
dir: "%kernel.root_dir%/config/exception/mapping"

0 commit comments

Comments
 (0)