Make Types throw instead of returning Utils::undefined()

This commit is contained in:
Daniel Tschinder 2018-04-24 15:14:31 +02:00
parent ec0985619f
commit f140149127
14 changed files with 117 additions and 70 deletions

View File

@ -30,11 +30,12 @@ class UrlType extends ScalarType
* *
* @param mixed $value * @param mixed $value
* @return mixed * @return mixed
* @throws Error
*/ */
public function parseValue($value) public function parseValue($value)
{ {
if (!is_string($value) || !filter_var($value, FILTER_VALIDATE_URL)) { // quite naive, but after all this is example if (!is_string($value) || !filter_var($value, FILTER_VALIDATE_URL)) { // quite naive, but after all this is example
throw new \UnexpectedValueException("Cannot represent value as URL: " . Utils::printSafe($value)); throw new Error("Cannot represent value as URL: " . Utils::printSafe($value));
} }
return $value; return $value;
} }

View File

@ -1197,8 +1197,7 @@ class Executor
} }
/** /**
* Complete a Scalar or Enum by serializing to a valid value, returning * Complete a Scalar or Enum by serializing to a valid value, throwing if serialization is not possible.
* null if serialization is not possible.
* *
* @param LeafType $returnType * @param LeafType $returnType
* @param $result * @param $result
@ -1207,14 +1206,21 @@ class Executor
*/ */
private function completeLeafValue(LeafType $returnType, &$result) private function completeLeafValue(LeafType $returnType, &$result)
{ {
$serializedResult = $returnType->serialize($result); try {
return $returnType->serialize($result);
if (Utils::isInvalid($serializedResult)) { } catch (\Exception $error) {
throw new InvariantViolation( throw new InvariantViolation(
'Expected a value of type "'. Utils::printSafe($returnType) . '" but received: ' . Utils::printSafe($result) 'Expected a value of type "'. Utils::printSafe($returnType) . '" but received: ' . Utils::printSafe($result),
0,
$error
);
} catch (\Throwable $error) {
throw new InvariantViolation(
'Expected a value of type "'. Utils::printSafe($returnType) . '" but received: ' . Utils::printSafe($result),
0,
$error
); );
} }
return $serializedResult;
} }
/** /**

View File

@ -1,7 +1,9 @@
<?php <?php
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error;
use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
/** /**
@ -32,22 +34,30 @@ class BooleanType extends ScalarType
/** /**
* @param mixed $value * @param mixed $value
* @return bool * @return bool
* @throws Error
*/ */
public function parseValue($value) public function parseValue($value)
{ {
return is_bool($value) ? $value : Utils::undefined(); if (is_bool($value)) {
return $value;
}
throw new Error("Cannot represent value as boolean: " . Utils::printSafe($value));
} }
/** /**
* @param $valueNode * @param Node $valueNode
* @param array|null $variables * @param array|null $variables
* @return bool|null * @return bool|null
* @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, array $variables = null)
{ {
if ($valueNode instanceof BooleanValueNode) { if ($valueNode instanceof BooleanValueNode) {
return (bool) $valueNode->value; return (bool) $valueNode->value;
} }
return Utils::undefined();
// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
} }
} }

View File

@ -1,6 +1,8 @@
<?php <?php
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error;
use GraphQL\Language\AST\Node;
use GraphQL\Utils\AST; use GraphQL\Utils\AST;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
@ -25,10 +27,6 @@ class CustomScalarType extends ScalarType
*/ */
public function parseValue($value) public function parseValue($value)
{ {
if (Utils::isInvalid($value)) {
return Utils::undefined();
}
if (isset($this->config['parseValue'])) { if (isset($this->config['parseValue'])) {
return call_user_func($this->config['parseValue'], $value); return call_user_func($this->config['parseValue'], $value);
} else { } else {
@ -37,9 +35,10 @@ class CustomScalarType extends ScalarType
} }
/** /**
* @param $valueNode * @param Node $valueNode
* @param array|null $variables * @param array|null $variables
* @return mixed * @return mixed
* @throws \Exception
*/ */
public function parseLiteral(/* GraphQL\Language\AST\ValueNode */ $valueNode, array $variables = null) public function parseLiteral(/* GraphQL\Language\AST\ValueNode */ $valueNode, array $variables = null)
{ {

View File

@ -1,6 +1,7 @@
<?php <?php
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\EnumTypeDefinitionNode; use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumValueNode; use GraphQL\Language\AST\EnumValueNode;
@ -103,7 +104,8 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
/** /**
* @param $value * @param $value
* @return null * @return mixed
* @throws Error
*/ */
public function serialize($value) public function serialize($value)
{ {
@ -112,36 +114,44 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
return $lookup[$value]->name; return $lookup[$value]->name;
} }
return Utils::undefined(); throw new Error("Cannot serialize value as enum: " . Utils::printSafe($value));
} }
/** /**
* @param $value * @param $value
* @return null * @return mixed
* @throws Error
*/ */
public function parseValue($value) public function parseValue($value)
{ {
$lookup = $this->getNameLookup(); $lookup = $this->getNameLookup();
return isset($lookup[$value]) ? $lookup[$value]->value : Utils::undefined(); if (isset($lookup[$value])) {
return $lookup[$value]->value;
}
throw new Error("Cannot represent value as enum: " . Utils::printSafe($value));
} }
/** /**
* @param $value * @param $valueNode
* @param array|null $variables * @param array|null $variables
* @return null * @return null
* @throws \Exception
*/ */
public function parseLiteral($value, array $variables = null) public function parseLiteral($valueNode, array $variables = null)
{ {
if ($value instanceof EnumValueNode) { if ($valueNode instanceof EnumValueNode) {
$lookup = $this->getNameLookup(); $lookup = $this->getNameLookup();
if (isset($lookup[$value->value])) { if (isset($lookup[$valueNode->value])) {
$enumValue = $lookup[$value->value]; $enumValue = $lookup[$valueNode->value];
if ($enumValue) { if ($enumValue) {
return $enumValue->value; return $enumValue->value;
} }
} }
} }
return null;
// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
} }
/** /**

View File

@ -49,13 +49,16 @@ values as specified by
* @param $valueNode * @param $valueNode
* @param array|null $variables * @param array|null $variables
* @return float|null * @return float|null
* @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, array $variables = null)
{ {
if ($valueNode instanceof FloatValueNode || $valueNode instanceof IntValueNode) { if ($valueNode instanceof FloatValueNode || $valueNode instanceof IntValueNode) {
return (float) $valueNode->value; return (float) $valueNode->value;
} }
return Utils::undefined();
// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
} }
private function coerceFloat($value) { private function coerceFloat($value) {

View File

@ -3,6 +3,7 @@ namespace GraphQL\Type\Definition;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\IntValueNode; use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\StringValueNode; use GraphQL\Language\AST\StringValueNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
@ -30,6 +31,7 @@ When expected as an input type, any string (such as `"4"`) or integer
/** /**
* @param mixed $value * @param mixed $value
* @return string * @return string
* @throws Error
*/ */
public function serialize($value) public function serialize($value)
{ {
@ -51,22 +53,30 @@ When expected as an input type, any string (such as `"4"`) or integer
/** /**
* @param mixed $value * @param mixed $value
* @return string * @return string
* @throws Error
*/ */
public function parseValue($value) public function parseValue($value)
{ {
return (is_string($value) || is_int($value)) ? (string) $value : Utils::undefined(); if (is_string($value) || is_int($value)) {
return (string) $value;
}
throw new Error("Cannot represent value as ID: " . Utils::printSafe($value));
} }
/** /**
* @param $ast * @param Node $valueNode
* @param array|null $variables * @param array|null $variables
* @return null|string * @return null|string
* @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, array $variables = null)
{ {
if ($valueNode instanceof StringValueNode || $valueNode instanceof IntValueNode) { if ($valueNode instanceof StringValueNode || $valueNode instanceof IntValueNode) {
return $valueNode->value; return $valueNode->value;
} }
return Utils::undefined();
// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
} }
} }

View File

@ -55,6 +55,7 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
* @param $valueNode * @param $valueNode
* @param array|null $variables * @param array|null $variables
* @return int|null * @return int|null
* @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, array $variables = null)
{ {
@ -64,7 +65,9 @@ values. Int can represent values between -(2^31) and 2^31 - 1. ';
return $val; return $val;
} }
} }
return Utils::undefined();
// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
} }
private function coerceInt($value) { private function coerceInt($value) {

View File

@ -1,6 +1,7 @@
<?php <?php
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use GraphQL\Error\Error;
use \GraphQL\Language\AST\Node; use \GraphQL\Language\AST\Node;
/* /*
@ -15,27 +16,30 @@ interface LeafType
* *
* @param mixed $value * @param mixed $value
* @return mixed * @return mixed
* @throws Error
*/ */
public function serialize($value); public function serialize($value);
/** /**
* Parses an externally provided value (query variable) to use as an input * Parses an externally provided value (query variable) to use as an input
* *
* In the case of an invalid value this method must return Utils::undefined() * In the case of an invalid value this method must throw an Exception
* *
* @param mixed $value * @param mixed $value
* @return mixed * @return mixed
* @throws Error
*/ */
public function parseValue($value); public function parseValue($value);
/** /**
* Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input * Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input
* *
* In the case of an invalid value this method must return Utils::undefined() * In the case of an invalid node or value this method must throw an Exception
* *
* @param Node $valueNode * @param Node $valueNode
* @param array|null $variables * @param array|null $variables
* @return mixed * @return mixed
* @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null); public function parseLiteral($valueNode, array $variables = null);
} }

View File

@ -60,13 +60,16 @@ represent free-form human-readable text.';
* @param $valueNode * @param $valueNode
* @param array|null $variables * @param array|null $variables
* @return null|string * @return null|string
* @throws \Exception
*/ */
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, array $variables = null)
{ {
if ($valueNode instanceof StringValueNode) { if ($valueNode instanceof StringValueNode) {
return $valueNode->value; return $valueNode->value;
} }
return Utils::undefined();
// Intentionally without message, as all information already in wrapped Exception
throw new \Exception();
} }
private function coerceString($value) { private function coerceString($value) {

View File

@ -209,10 +209,19 @@ class AST
if ($type instanceof ScalarType || $type instanceof EnumType) { if ($type instanceof ScalarType || $type instanceof EnumType) {
// Since value is an internally represented value, it must be serialized // Since value is an internally represented value, it must be serialized
// to an externally represented value before converting into an AST. // to an externally represented value before converting into an AST.
try {
$serialized = $type->serialize($value); $serialized = $type->serialize($value);
if (null === $serialized || Utils::isInvalid($serialized)) { } catch (\Exception $error) {
if ($error instanceof Error && $type instanceof EnumType) {
return null; return null;
} }
throw $error;
} catch (\Throwable $error) {
if ($error instanceof Error && $type instanceof EnumType) {
return null;
}
throw $error;
}
// Others serialize based on their corresponding PHP scalar types. // Others serialize based on their corresponding PHP scalar types.
if (is_bool($serialized)) { if (is_bool($serialized)) {
@ -400,18 +409,12 @@ class AST
// Invalid values represent a failure to parse correctly, in which case // Invalid values represent a failure to parse correctly, in which case
// no value is returned. // no value is returned.
try { try {
$result = $type->parseLiteral($valueNode, $variables); return $type->parseLiteral($valueNode, $variables);
} catch (\Exception $error) { } catch (\Exception $error) {
return $undefined; return $undefined;
} catch (\Throwable $error) { } catch (\Throwable $error) {
return $undefined; return $undefined;
} }
if (Utils::isInvalid($result)) {
return $undefined;
}
return $result;
} }
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.'); throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
@ -420,7 +423,7 @@ class AST
/** /**
* Produces a PHP value given a GraphQL Value AST. * Produces a PHP value given a GraphQL Value AST.
* *
* Unlike `valueFromAST()`, no type is provided. The resulting JavaScript value * Unlike `valueFromAST()`, no type is provided. The resulting PHP value
* will reflect the provided GraphQL value AST. * will reflect the provided GraphQL value AST.
* *
* | GraphQL Value | PHP Value | * | GraphQL Value | PHP Value |
@ -471,7 +474,7 @@ class AST
); );
case $valueNode instanceof VariableNode: case $valueNode instanceof VariableNode:
$variableName = $valueNode->name->value; $variableName = $valueNode->name->value;
return ($variables && isset($variables[$variableName]) && !Utils::isInvalid($variables[$variableName])) return ($variables && isset($variables[$variableName]))
? $variables[$variableName] ? $variables[$variableName]
: null; : null;
} }

View File

@ -46,14 +46,7 @@ class Value
// throw to indicate failure. If it throws, maintain a reference to // throw to indicate failure. If it throws, maintain a reference to
// the original error. // the original error.
try { try {
$parseResult = $type->parseValue($value); return self::ofValue($type->parseValue($value));
if (Utils::isInvalid($parseResult)) {
return self::ofErrors([
self::coercionError("Expected type {$type->name}", $blameNode, $path),
]);
}
return self::ofValue($parseResult);
} catch (\Exception $error) { } catch (\Exception $error) {
return self::ofErrors([ return self::ofErrors([
self::coercionError( self::coercionError(

View File

@ -173,20 +173,9 @@ class ValuesOfCorrectType extends AbstractValidationRule
} }
// Scalars determine if a literal value is valid via parseLiteral() which // Scalars determine if a literal value is valid via parseLiteral() which
// may throw or return an invalid value to indicate failure. // may throw to indicate failure.
try { try {
$parseResult = $type->parseLiteral($node); $type->parseLiteral($node);
if (Utils::isInvalid($parseResult)) {
$context->reportError(
new Error(
self::badValueMessage(
(string) $locationType,
Printer::doPrint($node)
),
$node
)
);
}
} catch (\Exception $error) { } catch (\Exception $error) {
// Ensure a reference to the original error is maintained. // Ensure a reference to the original error is maintained.
$context->reportError( $context->reportError(

View File

@ -1,6 +1,7 @@
<?php <?php
namespace GraphQL\Tests\Executor; namespace GraphQL\Tests\Executor;
use GraphQL\Error\Error;
use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\ScalarType;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
@ -53,28 +54,40 @@ class ComplexScalar extends ScalarType
public $name = 'ComplexScalar'; public $name = 'ComplexScalar';
/**
* {@inheritdoc}
*/
public function serialize($value) public function serialize($value)
{ {
if ($value === 'DeserializedValue') { if ($value === 'DeserializedValue') {
return 'SerializedValue'; return 'SerializedValue';
} }
return null;
throw new Error("Cannot serialize value as ComplexScalar: " . Utils::printSafe($value));
} }
/**
* {@inheritdoc}
*/
public function parseValue($value) public function parseValue($value)
{ {
if ($value === 'SerializedValue') { if ($value === 'SerializedValue') {
return 'DeserializedValue'; return 'DeserializedValue';
} }
return Utils::undefined();
throw new Error("Cannot represent value as ComplexScalar: " . Utils::printSafe($value));
} }
/**
* {@inheritdoc}
*/
public function parseLiteral($valueNode, array $variables = null) public function parseLiteral($valueNode, array $variables = null)
{ {
if ($valueNode->value === 'SerializedValue') { if ($valueNode->value === 'SerializedValue') {
return 'DeserializedValue'; return 'DeserializedValue';
} }
return Utils::undefined();
throw new Error("Cannot represent literal as ComplexScalar: " . Utils::printSafe($valueNode->value));
} }
} }