mirror of
https://github.com/retailcrm/graphql-php.git
synced 2025-02-06 07:49:24 +03:00
Fix unhandled error when parsing custom scalar literals.
This factors out the enum value validation from scalar value validation and ensures the same try/catch is used in isValidLiteralValue as isValidPHPValue and protecting from errors in valueFromAST. ref: graphql/graphql-js#1126
This commit is contained in:
parent
481cdc9a82
commit
f661f38215
@ -936,7 +936,12 @@ const UNION_TYPE_DEFINITION = "UnionTypeDefinition";
|
|||||||
const ENUM_TYPE_DEFINITION = "EnumTypeDefinition";
|
const ENUM_TYPE_DEFINITION = "EnumTypeDefinition";
|
||||||
const ENUM_VALUE_DEFINITION = "EnumValueDefinition";
|
const ENUM_VALUE_DEFINITION = "EnumValueDefinition";
|
||||||
const INPUT_OBJECT_TYPE_DEFINITION = "InputObjectTypeDefinition";
|
const INPUT_OBJECT_TYPE_DEFINITION = "InputObjectTypeDefinition";
|
||||||
|
const SCALAR_TYPE_EXTENSION = "ScalarTypeExtension";
|
||||||
const OBJECT_TYPE_EXTENSION = "ObjectTypeExtension";
|
const OBJECT_TYPE_EXTENSION = "ObjectTypeExtension";
|
||||||
|
const INTERFACE_TYPE_EXTENSION = "InterfaceTypeExtension";
|
||||||
|
const UNION_TYPE_EXTENSION = "UnionTypeExtension";
|
||||||
|
const ENUM_TYPE_EXTENSION = "EnumTypeExtension";
|
||||||
|
const INPUT_OBJECT_TYPE_EXTENSION = "InputObjectTypeExtension";
|
||||||
const DIRECTIVE_DEFINITION = "DirectiveDefinition";
|
const DIRECTIVE_DEFINITION = "DirectiveDefinition";
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -273,31 +273,36 @@ class Values
|
|||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::invariant($type instanceof EnumType || $type instanceof ScalarType, 'Must be input type');
|
if ($type instanceof EnumType) {
|
||||||
|
if (!is_string($value) || !$type->getValue($value)) {
|
||||||
|
$printed = Utils::printSafeJson($value);
|
||||||
try {
|
return ["Expected type \"{$type->name}\", found $printed."];
|
||||||
// Scalar/Enum input checks to ensure the type can parse the value to
|
|
||||||
// a non-null value.
|
|
||||||
|
|
||||||
if (!$type->isValidValue($value)) {
|
|
||||||
$v = Utils::printSafeJson($value);
|
|
||||||
return [
|
|
||||||
"Expected type \"{$type->name}\", found $v."
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
|
||||||
return [
|
return [];
|
||||||
"Expected type \"{$type->name}\", found " . Utils::printSafeJson($value) . ': ' .
|
|
||||||
$e->getMessage()
|
|
||||||
];
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return [
|
|
||||||
"Expected type \"{$type->name}\", found " . Utils::printSafeJson($value) . ': ' .
|
|
||||||
$e->getMessage()
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Utils::invariant($type instanceof ScalarType, 'Must be a scalar type');
|
||||||
|
/** @var ScalarType $type */
|
||||||
|
|
||||||
|
// Scalars determine if a value is valid via parseValue().
|
||||||
|
try {
|
||||||
|
$parseResult = $type->parseValue($value);
|
||||||
|
if (Utils::isInvalid($parseResult)) {
|
||||||
|
$printed = Utils::printSafeJson($value);
|
||||||
|
return [
|
||||||
|
"Expected type \"{$type->name}\", found $printed."
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (\Exception $error) {
|
||||||
|
$printed = Utils::printSafeJson($value);
|
||||||
|
$message = $error->getMessage();
|
||||||
|
return ["Expected type \"{$type->name}\", found $printed; $message"];
|
||||||
|
} catch (\Throwable $error) {
|
||||||
|
$printed = Utils::printSafeJson($value);
|
||||||
|
$message = $error->getMessage();
|
||||||
|
return ["Expected type \"{$type->name}\", found $printed; $message"];
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -370,12 +375,34 @@ class Values
|
|||||||
return $coercedObj;
|
return $coercedObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::invariant($type instanceof EnumType || $type instanceof ScalarType, 'Must be input type');
|
if ($type instanceof EnumType) {
|
||||||
|
if (!is_string($value) || !$type->getValue($value)) {
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if ($type->isValidValue($value)) {
|
$enumValue = $type->getValue($value);
|
||||||
return $type->parseValue($value);
|
if (!$enumValue) {
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $enumValue->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $undefined;
|
Utils::invariant($type instanceof ScalarType, 'Must be a scalar type');
|
||||||
|
/** @var ScalarType $type */
|
||||||
|
|
||||||
|
// Scalars determine if a value is valid via parseValue().
|
||||||
|
try {
|
||||||
|
$parseResult = $type->parseValue($value);
|
||||||
|
if (Utils::isInvalid($parseResult)) {
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
} catch (\Exception $error) {
|
||||||
|
return $undefined;
|
||||||
|
} catch (\Throwable $error) {
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parseResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,25 +115,6 @@ class EnumType extends Type implements InputType, OutputType, LeafType
|
|||||||
return Utils::undefined();
|
return Utils::undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $value
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isValidValue($value)
|
|
||||||
{
|
|
||||||
return is_string($value) && $this->getNameLookup()->offsetExists($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $valueNode
|
|
||||||
* @param array|null $variables
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isValidLiteral($valueNode, array $variables = null)
|
|
||||||
{
|
|
||||||
return $valueNode instanceof EnumValueNode && $this->getNameLookup()->offsetExists($valueNode->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $value
|
* @param $value
|
||||||
* @return null
|
* @return null
|
||||||
|
@ -38,17 +38,4 @@ interface LeafType
|
|||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function parseLiteral($valueNode, array $variables = null);
|
public function parseLiteral($valueNode, array $variables = null);
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $value
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isValidValue($value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Node $valueNode
|
|
||||||
* @param array|null $variables
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isValidLiteral($valueNode, array $variables = null);
|
|
||||||
}
|
}
|
||||||
|
@ -38,28 +38,4 @@ 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.
|
|
||||||
*
|
|
||||||
* @param $value
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isValidValue($value)
|
|
||||||
{
|
|
||||||
return !Utils::isInvalid($this->parseValue($value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if an internal value is valid for this type.
|
|
||||||
* Equivalent to checking for if the parsedLiteral is nullish.
|
|
||||||
*
|
|
||||||
* @param $valueNode
|
|
||||||
* @param array|null $variables
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isValidLiteral($valueNode, array $variables = null)
|
|
||||||
{
|
|
||||||
return !Utils::isInvalid($this->parseLiteral($valueNode, $variables));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -383,15 +383,37 @@ class AST
|
|||||||
return $coercedObj;
|
return $coercedObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$type instanceof ScalarType && !$type instanceof EnumType) {
|
if ($type instanceof EnumType) {
|
||||||
throw new InvariantViolation('Must be input type');
|
if (!$valueNode instanceof EnumValueNode) {
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
$enumValue = $type->getValue($valueNode->value);
|
||||||
|
if (!$enumValue) {
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $enumValue->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($type->isValidLiteral($valueNode, $variables)) {
|
Utils::invariant($type instanceof ScalarType, 'Must be scalar type');
|
||||||
return $type->parseLiteral($valueNode, $variables);
|
/** @var ScalarType $type */
|
||||||
|
|
||||||
|
// Scalars fulfill parsing a literal value via parseLiteral().
|
||||||
|
// Invalid values represent a failure to parse correctly, in which case
|
||||||
|
// no value is returned.
|
||||||
|
try {
|
||||||
|
$result = $type->parseLiteral($valueNode, $variables);
|
||||||
|
} catch (\Exception $error) {
|
||||||
|
return $undefined;
|
||||||
|
} catch (\Throwable $error) {
|
||||||
|
return $undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $undefined;
|
if (Utils::isInvalid($result)) {
|
||||||
|
return $undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
namespace GraphQL\Validator;
|
namespace GraphQL\Validator;
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Language\AST\EnumValueNode;
|
||||||
use GraphQL\Language\AST\ListValueNode;
|
use GraphQL\Language\AST\ListValueNode;
|
||||||
use GraphQL\Language\AST\DocumentNode;
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
use GraphQL\Language\AST\NodeKind;
|
||||||
@ -309,12 +310,33 @@ class DocumentValidator
|
|||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::invariant($type instanceof ScalarType || $type instanceof EnumType, 'Must be input type');
|
if ($type instanceof EnumType) {
|
||||||
|
if (!$valueNode instanceof EnumValueNode || !$type->getValue($valueNode->value)) {
|
||||||
|
$printed = Printer::doPrint($valueNode);
|
||||||
|
return ["Expected type \"{$type->name}\", found $printed."];
|
||||||
|
}
|
||||||
|
|
||||||
// Scalars determine if a literal values is valid.
|
return [];
|
||||||
if (!$type->isValidLiteral($valueNode)) {
|
}
|
||||||
|
|
||||||
|
Utils::invariant($type instanceof ScalarType, 'Must be a scalar type');
|
||||||
|
/** @var ScalarType $type */
|
||||||
|
|
||||||
|
// Scalars determine if a literal values is valid via parseLiteral().
|
||||||
|
try {
|
||||||
|
$parseResult = $type->parseLiteral($valueNode);
|
||||||
|
if (Utils::isInvalid($parseResult)) {
|
||||||
|
$printed = Printer::doPrint($valueNode);
|
||||||
|
return ["Expected type \"{$type->name}\", found $printed."];
|
||||||
|
}
|
||||||
|
} catch (\Exception $error) {
|
||||||
$printed = Printer::doPrint($valueNode);
|
$printed = Printer::doPrint($valueNode);
|
||||||
return [ "Expected type \"{$type->name}\", found $printed." ];
|
$message = $error->getMessage();
|
||||||
|
return ["Expected type \"{$type->name}\", found $printed; $message"];
|
||||||
|
} catch (\Throwable $error) {
|
||||||
|
$printed = Printer::doPrint($valueNode);
|
||||||
|
$message = $error->getMessage();
|
||||||
|
return ["Expected type \"{$type->name}\", found $printed; $message"];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
@ -260,17 +260,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$invalidScalar = new CustomScalarType([
|
|
||||||
'name' => 'Invalid',
|
|
||||||
'serialize' => function ($value) { return $value; },
|
|
||||||
'parseLiteral' => function ($node) {
|
|
||||||
throw new \Exception('Invalid scalar is always invalid: ' . $node->value);
|
|
||||||
},
|
|
||||||
'parseValue' => function ($value) {
|
|
||||||
throw new \Exception('Invalid scalar is always invalid: ' . $value);
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
$anyScalar = new CustomScalarType([
|
$anyScalar = new CustomScalarType([
|
||||||
'name' => 'Any',
|
'name' => 'Any',
|
||||||
'serialize' => function ($value) { return $value; },
|
'serialize' => function ($value) { return $value; },
|
||||||
@ -278,6 +267,19 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
|||||||
'parseValue' => function ($value) { return $value; }, // Allows any value
|
'parseValue' => function ($value) { return $value; }, // Allows any value
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$invalidScalar = new CustomScalarType([
|
||||||
|
'name' => 'Invalid',
|
||||||
|
'serialize' => function ($value) {
|
||||||
|
return $value;
|
||||||
|
},
|
||||||
|
'parseLiteral' => function ($node) {
|
||||||
|
throw new \Exception('Invalid scalar is always invalid: ' . $node->value);
|
||||||
|
},
|
||||||
|
'parseValue' => function ($node) {
|
||||||
|
throw new \Exception('Invalid scalar is always invalid: ' . $node);
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
$queryRoot = new ObjectType([
|
$queryRoot = new ObjectType([
|
||||||
'name' => 'QueryRoot',
|
'name' => 'QueryRoot',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
@ -293,14 +295,16 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
|||||||
'dogOrHuman' => ['type' => $DogOrHuman],
|
'dogOrHuman' => ['type' => $DogOrHuman],
|
||||||
'humanOrAlien' => ['type' => $HumanOrAlien],
|
'humanOrAlien' => ['type' => $HumanOrAlien],
|
||||||
'complicatedArgs' => ['type' => $ComplicatedArgs],
|
'complicatedArgs' => ['type' => $ComplicatedArgs],
|
||||||
'invalidArg' => [
|
|
||||||
'args' => ['arg' => ['type' => $invalidScalar]],
|
|
||||||
'type' => Type::string(),
|
|
||||||
],
|
|
||||||
'anyArg' => [
|
'anyArg' => [
|
||||||
'args' => ['arg' => ['type' => $anyScalar]],
|
'args' => ['arg' => ['type' => $anyScalar]],
|
||||||
'type' => Type::string(),
|
'type' => Type::string(),
|
||||||
],
|
],
|
||||||
|
'invalidArg' => [
|
||||||
|
'args' => [
|
||||||
|
'arg' => ['type' => $invalidScalar]
|
||||||
|
],
|
||||||
|
'type' => Type::string(),
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Tests\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
|
use GraphQL\Error\FormattedError;
|
||||||
use GraphQL\Validator\DocumentValidator;
|
use GraphQL\Validator\DocumentValidator;
|
||||||
use GraphQL\Validator\Rules\QueryComplexity;
|
use GraphQL\Validator\Rules\QueryComplexity;
|
||||||
|
|
||||||
@ -26,16 +27,32 @@ class ValidationTest extends TestCase
|
|||||||
}
|
}
|
||||||
');
|
');
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
public function testAllowsSettingRulesGlobally()
|
|
||||||
{
|
|
||||||
$rule = new QueryComplexity(0);
|
|
||||||
|
|
||||||
DocumentValidator::addRule($rule);
|
/**
|
||||||
$instance = DocumentValidator::getRule(QueryComplexity::class);
|
* @it detects bad scalar parse
|
||||||
$this->assertSame($rule, $instance);
|
*/
|
||||||
|
public function testDetectsBadScalarParse()
|
||||||
|
{
|
||||||
|
$doc = '
|
||||||
|
query {
|
||||||
|
invalidArg(arg: "bad value")
|
||||||
|
}
|
||||||
|
';
|
||||||
|
|
||||||
|
$expectedError = [
|
||||||
|
'message' => "Argument \"arg\" has invalid value \"bad value\".
|
||||||
|
Expected type \"Invalid\", found \"bad value\"; Invalid scalar is always invalid: bad value",
|
||||||
|
'locations' => [ ['line' => 3, 'column' => 25] ]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->expectInvalid(
|
||||||
|
$this->getTestSchema(),
|
||||||
|
null,
|
||||||
|
$doc,
|
||||||
|
[$expectedError]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
public function testPassesValidationWithEmptyRules()
|
public function testPassesValidationWithEmptyRules()
|
||||||
{
|
{
|
||||||
$query = '{invalid}';
|
$query = '{invalid}';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user