diff --git a/src/Executor/Values.php b/src/Executor/Values.php index 1e9c69d..552c3ea 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -247,7 +247,7 @@ class Values // Scalar/Enum input checks to ensure the type can parse the value to // a non-null value. $parseResult = $type->parseValue($value); - if (null === $parseResult) { + if (null === $parseResult && !$type->isValidValue($value)) { $v = Utils::printSafe($value); return [ "Expected type \"{$type->name}\", found $v." diff --git a/src/Type/Definition/EnumType.php b/src/Type/Definition/EnumType.php index 4edcccd..0469fa5 100644 --- a/src/Type/Definition/EnumType.php +++ b/src/Type/Definition/EnumType.php @@ -91,6 +91,24 @@ class EnumType extends Type implements InputType, OutputType, LeafType 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 * @return null diff --git a/src/Type/Definition/LeafType.php b/src/Type/Definition/LeafType.php index 0c61a02..a0bd30f 100644 --- a/src/Type/Definition/LeafType.php +++ b/src/Type/Definition/LeafType.php @@ -31,4 +31,16 @@ interface LeafType * @return mixed */ 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); } diff --git a/src/Type/Definition/ScalarType.php b/src/Type/Definition/ScalarType.php index d072f89..6d35455 100644 --- a/src/Type/Definition/ScalarType.php +++ b/src/Type/Definition/ScalarType.php @@ -34,4 +34,28 @@ abstract class ScalarType extends Type implements OutputType, InputType, LeafTyp 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); + } } diff --git a/src/Utils/AST.php b/src/Utils/AST.php index 3b793fd..1eaed83 100644 --- a/src/Utils/AST.php +++ b/src/Utils/AST.php @@ -307,9 +307,9 @@ class AST if ($type instanceof LeafType) { $parsed = $type->parseLiteral($valueNode); - if (null === $parsed) { - // null represent a failure to parse correctly, - // in which case no value is returned. + if (null === $parsed && !$type->isValidLiteral($valueNode)) { + // Invalid values represent a failure to parse correctly, in which case + // no value is returned. return $undefined; } diff --git a/src/Utils/MixedStore.php b/src/Utils/MixedStore.php index 9811ba7..83342b6 100644 --- a/src/Utils/MixedStore.php +++ b/src/Utils/MixedStore.php @@ -43,6 +43,16 @@ class MixedStore implements \ArrayAccess */ private $lastArrayValue; + /** + * @var mixed + */ + private $nullValue; + + /** + * @var bool + */ + private $nullValueIsSet = false; + /** * MixedStore constructor. */ @@ -83,6 +93,9 @@ class MixedStore implements \ArrayAccess } } } + if (null === $offset) { + return $this->nullValueIsSet; + } return false; } @@ -114,6 +127,9 @@ class MixedStore implements \ArrayAccess } } } + if (null === $offset) { + return $this->nullValue; + } return null; } @@ -138,6 +154,9 @@ class MixedStore implements \ArrayAccess } else if (is_array($offset)) { $this->arrayKeys[] = $offset; $this->arrayValues[] = $value; + } else if (null === $offset) { + $this->nullValue = $value; + $this->nullValueIsSet = true; } else { 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->arrayValues, $index, 1); } + } else if (null === $offset) { + $this->nullValue = null; + $this->nullValueIsSet = false; } } } diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index 61d68b6..b5fae95 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -1,22 +1,16 @@ parseLiteral($valueNode); - - if (null === $parseResult) { + // Scalars must parse to a non-null value + if (!$type->isValidLiteral($valueNode)) { $printed = Printer::doPrint($valueNode); return [ "Expected type \"{$type->name}\", found $printed." ]; } diff --git a/tests/Utils/ValueFromAstTest.php b/tests/Utils/ValueFromAstTest.php index 1bceab2..755ca31 100644 --- a/tests/Utils/ValueFromAstTest.php +++ b/tests/Utils/ValueFromAstTest.php @@ -72,6 +72,7 @@ class ValueFromAstTest extends \PHPUnit_Framework_TestCase 'RED' => ['value' => 1], 'GREEN' => ['value' => 2], '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, '"BLUE"', Utils::undefined()); $this->runTestCase($testEnum, 'null', null); + $this->runTestCase($testEnum, 'NULL', null); } /** diff --git a/tests/Validator/ArgumentsOfCorrectTypeTest.php b/tests/Validator/ArgumentsOfCorrectTypeTest.php index ed2175f..96b1f27 100644 --- a/tests/Validator/ArgumentsOfCorrectTypeTest.php +++ b/tests/Validator/ArgumentsOfCorrectTypeTest.php @@ -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 */ diff --git a/tests/Validator/TestCase.php b/tests/Validator/TestCase.php index d2af941..387012a 100644 --- a/tests/Validator/TestCase.php +++ b/tests/Validator/TestCase.php @@ -183,6 +183,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase 'BLACK' => [ 'value' => 1 ], 'TAN' => [ 'value' => 2 ], 'SPOTTED' => [ 'value' => 3 ], + 'NO_FUR' => [ 'value' => null ], ], ]);