Allow null values for enums

This commit is contained in:
Vladimir Razuvaev 2017-07-04 20:19:52 +07:00
parent f569c6de2d
commit b47c87f793
10 changed files with 99 additions and 15 deletions

View File

@ -247,7 +247,7 @@ class Values
// Scalar/Enum input checks to ensure the type can parse the value to // Scalar/Enum input checks to ensure the type can parse the value to
// a non-null value. // a non-null value.
$parseResult = $type->parseValue($value); $parseResult = $type->parseValue($value);
if (null === $parseResult) { if (null === $parseResult && !$type->isValidValue($value)) {
$v = Utils::printSafe($value); $v = Utils::printSafe($value);
return [ return [
"Expected type \"{$type->name}\", found $v." "Expected type \"{$type->name}\", found $v."

View File

@ -91,6 +91,24 @@ class EnumType extends Type implements InputType, OutputType, LeafType
return isset($lookup[$value]) ? $lookup[$value]->name : null; return isset($lookup[$value]) ? $lookup[$value]->name : null;
} }
/**
* @param string $value
* @return bool
*/
public function isValidValue($value)
{
return is_string($value) && $this->getNameLookup()->offsetExists($value);
}
/**
* @param $valueNode
* @return bool
*/
public function isValidLiteral($valueNode)
{
return $valueNode instanceof EnumValueNode && $this->getNameLookup()->offsetExists($valueNode->value);
}
/** /**
* @param $value * @param $value
* @return null * @return null

View File

@ -31,4 +31,16 @@ interface LeafType
* @return mixed * @return mixed
*/ */
public function parseLiteral($valueNode); public function parseLiteral($valueNode);
/**
* @param string $value
* @return bool
*/
public function isValidValue($value);
/**
* @param \GraphQL\Language\AST\Node $valueNode
* @return mixed
*/
public function isValidLiteral($valueNode);
} }

View File

@ -34,4 +34,28 @@ abstract class ScalarType extends Type implements OutputType, InputType, LeafTyp
Utils::assertValidName($this->name); Utils::assertValidName($this->name);
} }
/**
* Determines if an internal value is valid for this type.
* Equivalent to checking for if the parsedValue is nullish.
*
* @param $value
* @return bool
*/
public function isValidValue($value)
{
return null !== $this->parseValue($value);
}
/**
* Determines if an internal value is valid for this type.
* Equivalent to checking for if the parsedLiteral is nullish.
*
* @param $valueNode
* @return bool
*/
public function isValidLiteral($valueNode)
{
return null !== $this->parseLiteral($valueNode);
}
} }

View File

@ -307,9 +307,9 @@ class AST
if ($type instanceof LeafType) { if ($type instanceof LeafType) {
$parsed = $type->parseLiteral($valueNode); $parsed = $type->parseLiteral($valueNode);
if (null === $parsed) { if (null === $parsed && !$type->isValidLiteral($valueNode)) {
// null represent a failure to parse correctly, // Invalid values represent a failure to parse correctly, in which case
// in which case no value is returned. // no value is returned.
return $undefined; return $undefined;
} }

View File

@ -43,6 +43,16 @@ class MixedStore implements \ArrayAccess
*/ */
private $lastArrayValue; private $lastArrayValue;
/**
* @var mixed
*/
private $nullValue;
/**
* @var bool
*/
private $nullValueIsSet = false;
/** /**
* MixedStore constructor. * MixedStore constructor.
*/ */
@ -83,6 +93,9 @@ class MixedStore implements \ArrayAccess
} }
} }
} }
if (null === $offset) {
return $this->nullValueIsSet;
}
return false; return false;
} }
@ -114,6 +127,9 @@ class MixedStore implements \ArrayAccess
} }
} }
} }
if (null === $offset) {
return $this->nullValue;
}
return null; return null;
} }
@ -138,6 +154,9 @@ class MixedStore implements \ArrayAccess
} else if (is_array($offset)) { } else if (is_array($offset)) {
$this->arrayKeys[] = $offset; $this->arrayKeys[] = $offset;
$this->arrayValues[] = $value; $this->arrayValues[] = $value;
} else if (null === $offset) {
$this->nullValue = $value;
$this->nullValueIsSet = true;
} else { } else {
throw new \InvalidArgumentException("Unexpected offset type: " . Utils::printSafe($offset)); throw new \InvalidArgumentException("Unexpected offset type: " . Utils::printSafe($offset));
} }
@ -165,6 +184,9 @@ class MixedStore implements \ArrayAccess
array_splice($this->arrayKeys, $index, 1); array_splice($this->arrayKeys, $index, 1);
array_splice($this->arrayValues, $index, 1); array_splice($this->arrayValues, $index, 1);
} }
} else if (null === $offset) {
$this->nullValue = null;
$this->nullValueIsSet = false;
} }
} }
} }

View File

@ -1,22 +1,16 @@
<?php <?php
namespace GraphQL\Validator; namespace GraphQL\Validator;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Language\AST\ListValueNode; use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NullValueNode; use GraphQL\Language\AST\NullValueNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\VariableNode; use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\Printer; use GraphQL\Language\Printer;
use GraphQL\Language\Visitor; use GraphQL\Language\Visitor;
use GraphQL\Language\VisitorOperation;
use GraphQL\Schema; use GraphQL\Schema;
use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\LeafType; use GraphQL\Type\Definition\LeafType;
use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\NonNull;
@ -231,11 +225,8 @@ class DocumentValidator
} }
if ($type instanceof LeafType) { if ($type instanceof LeafType) {
// Scalar/Enum input checks to ensure the type can parse the value to // Scalars must parse to a non-null value
// a non-null value. if (!$type->isValidLiteral($valueNode)) {
$parseResult = $type->parseLiteral($valueNode);
if (null === $parseResult) {
$printed = Printer::doPrint($valueNode); $printed = Printer::doPrint($valueNode);
return [ "Expected type \"{$type->name}\", found $printed." ]; return [ "Expected type \"{$type->name}\", found $printed." ];
} }

View File

@ -72,6 +72,7 @@ class ValueFromAstTest extends \PHPUnit_Framework_TestCase
'RED' => ['value' => 1], 'RED' => ['value' => 1],
'GREEN' => ['value' => 2], 'GREEN' => ['value' => 2],
'BLUE' => ['value' => 3], 'BLUE' => ['value' => 3],
'NULL' => ['value' => null],
] ]
]); ]);
@ -80,6 +81,7 @@ class ValueFromAstTest extends \PHPUnit_Framework_TestCase
$this->runTestCase($testEnum, '3', Utils::undefined()); $this->runTestCase($testEnum, '3', Utils::undefined());
$this->runTestCase($testEnum, '"BLUE"', Utils::undefined()); $this->runTestCase($testEnum, '"BLUE"', Utils::undefined());
$this->runTestCase($testEnum, 'null', null); $this->runTestCase($testEnum, 'null', null);
$this->runTestCase($testEnum, 'NULL', null);
} }
/** /**

View File

@ -132,6 +132,20 @@ class ArgumentsOfCorrectTypeTest extends TestCase
'); ');
} }
/**
* @it Enum with null value
*/
public function testEnumWithNullValue()
{
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
{
complicatedArgs {
enumArgField(enumArg: NO_FUR)
}
}
');
}
/** /**
* @it null into nullable type * @it null into nullable type
*/ */

View File

@ -183,6 +183,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
'BLACK' => [ 'value' => 1 ], 'BLACK' => [ 'value' => 1 ],
'TAN' => [ 'value' => 2 ], 'TAN' => [ 'value' => 2 ],
'SPOTTED' => [ 'value' => 3 ], 'SPOTTED' => [ 'value' => 3 ],
'NO_FUR' => [ 'value' => null ],
], ],
]); ]);