Skip to content

Commit 16a069a

Browse files
committed
Throw SerializationError over client safe Error when failing to serialize scalars
Resolves #570
1 parent 9445f7a commit 16a069a

File tree

13 files changed

+61
-216
lines changed

13 files changed

+61
-216
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ You can find and compare releases at the [GitHub release page](https://github.co
1414
- PHP version required: 7.4+
1515
- Propagate error message and stack trace for why leaf value serialization failed
1616
- Do not throw client safe `Error` when failing to serialize an Enum type
17+
- Throw `SerializationError` over client safe `Error` when failing to serialize scalars
1718

1819
### Added
1920

examples/01-blog/Blog/Type/Scalar/EmailType.php

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,27 @@
55
namespace GraphQL\Examples\Blog\Type\Scalar;
66

77
use GraphQL\Error\Error;
8-
use GraphQL\Language\AST\BooleanValueNode;
9-
use GraphQL\Language\AST\FloatValueNode;
10-
use GraphQL\Language\AST\IntValueNode;
8+
use GraphQL\Error\SerializationError;
119
use GraphQL\Language\AST\Node;
12-
use GraphQL\Language\AST\NullValueNode;
1310
use GraphQL\Language\AST\StringValueNode;
1411
use GraphQL\Type\Definition\ScalarType;
1512
use GraphQL\Utils\Utils;
16-
use UnexpectedValueException;
1713

1814
use function filter_var;
1915

2016
use const FILTER_VALIDATE_EMAIL;
2117

2218
class EmailType extends ScalarType
2319
{
24-
/**
25-
* Serializes an internal value to include in a response.
26-
*
27-
* Should throw an exception on invalid values.
28-
*
29-
* @param mixed $value
30-
*/
3120
public function serialize($value): string
3221
{
3322
if (! $this->isEmail($value)) {
34-
throw new UnexpectedValueException('Cannot represent value as email: ' . Utils::printSafe($value));
23+
throw new SerializationError('Cannot represent value as email: ' . Utils::printSafe($value));
3524
}
3625

3726
return $value;
3827
}
3928

40-
/**
41-
* Parses an externally provided value (query variable) to use as an input.
42-
*
43-
* Should throw an exception with a client friendly message on invalid values, @see ClientAware.
44-
*
45-
* @param mixed $value
46-
*/
4729
public function parseValue($value): string
4830
{
4931
if (! $this->isEmail($value)) {
@@ -53,14 +35,6 @@ public function parseValue($value): string
5335
return $value;
5436
}
5537

56-
/**
57-
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input.
58-
*
59-
* Should throw an exception with a client friendly message on invalid value nodes, @see ClientAware.
60-
*
61-
* @param IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|NullValueNode $valueNode
62-
* @param array<string, mixed>|null $variables
63-
*/
6438
public function parseLiteral(Node $valueNode, ?array $variables = null): string
6539
{
6640
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL

examples/01-blog/Blog/Type/Scalar/UrlType.php

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@
55
namespace GraphQL\Examples\Blog\Type\Scalar;
66

77
use GraphQL\Error\Error;
8-
use GraphQL\Language\AST\BooleanValueNode;
9-
use GraphQL\Language\AST\FloatValueNode;
10-
use GraphQL\Language\AST\IntValueNode;
8+
use GraphQL\Error\SerializationError;
119
use GraphQL\Language\AST\Node;
12-
use GraphQL\Language\AST\NullValueNode;
1310
use GraphQL\Language\AST\StringValueNode;
1411
use GraphQL\Type\Definition\ScalarType;
1512
use GraphQL\Utils\Utils;
16-
use UnexpectedValueException;
1713

1814
use function filter_var;
1915
use function is_string;
@@ -22,34 +18,16 @@
2218

2319
class UrlType extends ScalarType
2420
{
25-
/**
26-
* Serializes an internal value to include in a response.
27-
*
28-
* Should throw an exception on invalid values.
29-
*
30-
* @param mixed $value
31-
*
32-
* @return mixed
33-
*/
34-
public function serialize($value)
21+
public function serialize($value): string
3522
{
3623
if (! $this->isUrl($value)) {
37-
throw new UnexpectedValueException('Cannot represent value as URL: ' . Utils::printSafe($value));
24+
throw new SerializationError('Cannot represent value as URL: ' . Utils::printSafe($value));
3825
}
3926

4027
return $value;
4128
}
4229

43-
/**
44-
* Parses an externally provided value (query variable) to use as an input.
45-
*
46-
* Should throw an exception with a client friendly message on invalid values, @see ClientAware.
47-
*
48-
* @param mixed $value
49-
*
50-
* @return mixed
51-
*/
52-
public function parseValue($value)
30+
public function parseValue($value): string
5331
{
5432
if (! $this->isUrl($value)) {
5533
throw new Error('Cannot represent value as URL: ' . Utils::printSafe($value));
@@ -58,15 +36,7 @@ public function parseValue($value)
5836
return $value;
5937
}
6038

61-
/**
62-
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input.
63-
*
64-
* Should throw an exception with a client friendly message on invalid value nodes, @see ClientAware.
65-
*
66-
* @param IntValueNode|FloatValueNode|StringValueNode|BooleanValueNode|NullValueNode $valueNode
67-
* @param array<string, mixed>|null $variables
68-
*/
69-
public function parseLiteral(Node $valueNode, ?array $variables = null): ?string
39+
public function parseLiteral(Node $valueNode, ?array $variables = null): string
7040
{
7141
// Throwing GraphQL\Error\Error to benefit from GraphQL error location in query
7242
if (! ($valueNode instanceof StringValueNode)) {

phpcs.xml.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
<exclude name="SlevomatCodingStandard.PHP.RequireExplicitAssertion.RequiredExplicitAssertion" />
2222
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingAnyTypeHint" />
2323
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint" />
24+
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification" />
25+
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingAnyTypeHint" />
2426
<exclude name="SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint" />
2527
<exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingAnyTypeHint" />
2628
<exclude name="SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingNativeTypeHint" />

src/Error/SerializationError.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GraphQL\Error;
6+
7+
use Exception;
8+
9+
/**
10+
* Thrown when failing to serialize a scalar value.
11+
*
12+
* Not generally safe for clients, as the wrong given value could
13+
* be something not intended to ever be seen by clients.
14+
*/
15+
class SerializationError extends Exception
16+
{
17+
}

src/Type/Definition/BooleanType.php

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace GraphQL\Type\Definition;
66

7-
use Exception;
87
use GraphQL\Error\Error;
98
use GraphQL\Language\AST\BooleanValueNode;
109
use GraphQL\Language\AST\Node;
@@ -14,33 +13,22 @@
1413

1514
class BooleanType extends ScalarType
1615
{
17-
/** @var string */
1816
public $name = Type::BOOLEAN;
1917

20-
/** @var string */
2118
public $description = 'The `Boolean` scalar type represents `true` or `false`.';
2219

2320
/**
24-
* Serialize the given value to a boolean.
21+
* Serialize the given value to a bool.
2522
*
2623
* The GraphQL spec leaves this up to the implementations, so we just do what
2724
* PHP does natively to make this intuitive for developers.
28-
*
29-
* @param mixed $value
3025
*/
3126
public function serialize($value): bool
3227
{
3328
return (bool) $value;
3429
}
3530

36-
/**
37-
* @param mixed $value
38-
*
39-
* @return bool
40-
*
41-
* @throws Error
42-
*/
43-
public function parseValue($value)
31+
public function parseValue($value): bool
4432
{
4533
if (is_bool($value)) {
4634
return $value;
@@ -49,18 +37,13 @@ public function parseValue($value)
4937
throw new Error('Boolean cannot represent a non boolean value: ' . Utils::printSafe($value));
5038
}
5139

52-
/**
53-
* @param mixed[]|null $variables
54-
*
55-
* @throws Exception
56-
*/
57-
public function parseLiteral(Node $valueNode, ?array $variables = null)
40+
public function parseLiteral(Node $valueNode, ?array $variables = null): bool
5841
{
59-
if (! $valueNode instanceof BooleanValueNode) {
60-
// Intentionally without message, as all information already in wrapped Exception
61-
throw new Error();
42+
if ($valueNode instanceof BooleanValueNode) {
43+
return $valueNode->value;
6244
}
6345

64-
return $valueNode->value;
46+
// Intentionally without message, as all information already in wrapped Exception
47+
throw new Error();
6548
}
6649
}

src/Type/Definition/CustomScalarType.php

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace GraphQL\Type\Definition;
66

7-
use Exception;
87
use GraphQL\Language\AST\Node;
98
use GraphQL\Utils\AST;
109
use GraphQL\Utils\Utils;
@@ -14,21 +13,11 @@
1413

1514
class CustomScalarType extends ScalarType
1615
{
17-
/**
18-
* @param mixed $value
19-
*
20-
* @return mixed
21-
*/
2216
public function serialize($value)
2317
{
2418
return $this->config['serialize']($value);
2519
}
2620

27-
/**
28-
* @param mixed $value
29-
*
30-
* @return mixed
31-
*/
3221
public function parseValue($value)
3322
{
3423
if (isset($this->config['parseValue'])) {
@@ -38,13 +27,6 @@ public function parseValue($value)
3827
return $value;
3928
}
4029

41-
/**
42-
* @param mixed[]|null $variables
43-
*
44-
* @return mixed
45-
*
46-
* @throws Exception
47-
*/
4830
public function parseLiteral(Node $valueNode, ?array $variables = null)
4931
{
5032
if (isset($this->config['parseLiteral'])) {

src/Type/Definition/FloatType.php

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
namespace GraphQL\Type\Definition;
66

7-
use Exception;
87
use GraphQL\Error\Error;
8+
use GraphQL\Error\SerializationError;
99
use GraphQL\Language\AST\FloatValueNode;
1010
use GraphQL\Language\AST\IntValueNode;
1111
use GraphQL\Language\AST\Node;
@@ -19,28 +19,21 @@
1919

2020
class FloatType extends ScalarType
2121
{
22-
/** @var string */
2322
public $name = Type::FLOAT;
2423

25-
/** @var string */
2624
public $description =
2725
'The `Float` scalar type represents signed double-precision fractional
2826
values as specified by
2927
[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ';
3028

31-
/**
32-
* @param mixed $value
33-
*
34-
* @throws Error
35-
*/
3629
public function serialize($value): float
3730
{
3831
$float = is_numeric($value) || is_bool($value)
3932
? (float) $value
4033
: null;
4134

4235
if ($float === null || ! is_finite($float)) {
43-
throw new Error(
36+
throw new SerializationError(
4437
'Float cannot represent non numeric value: ' .
4538
Utils::printSafe($value)
4639
);
@@ -49,11 +42,6 @@ public function serialize($value): float
4942
return $float;
5043
}
5144

52-
/**
53-
* @param mixed $value
54-
*
55-
* @throws Error
56-
*/
5745
public function parseValue($value): float
5846
{
5947
$float = is_float($value) || is_int($value)
@@ -70,13 +58,6 @@ public function parseValue($value): float
7058
return $float;
7159
}
7260

73-
/**
74-
* @param mixed[]|null $variables
75-
*
76-
* @return float
77-
*
78-
* @throws Exception
79-
*/
8061
public function parseLiteral(Node $valueNode, ?array $variables = null)
8162
{
8263
if ($valueNode instanceof FloatValueNode || $valueNode instanceof IntValueNode) {

0 commit comments

Comments
 (0)