mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 22:36:02 +03:00
Validate literals in a single rule with finer precision
This generalizes the "arguments of correct type" and "default values of correct type" to a single rule "values of correct type" which has been re-written to rely on a traversal rather than the utility function `isValidLiteralValue`. To reduce breaking scope, this does not remove that utility even though it's no longer used directly within the library. Since the default values rule included another validation rule that rule was renamed to a more apt "variable default value allowed". This also includes the original errors from custom scalars in the validation error output, solving the remainder of graphql/graphql-js#821. ref: graphql/graphql-js#1144
This commit is contained in:
parent
17520876d8
commit
58e0c7a178
@ -115,7 +115,6 @@ class Values
|
|||||||
}
|
}
|
||||||
|
|
||||||
$coercedValues = [];
|
$coercedValues = [];
|
||||||
$undefined = Utils::undefined();
|
|
||||||
|
|
||||||
/** @var ArgumentNode[] $argNodeMap */
|
/** @var ArgumentNode[] $argNodeMap */
|
||||||
$argNodeMap = $argNodes ? Utils::keyMap($argNodes, function (ArgumentNode $arg) {
|
$argNodeMap = $argNodes ? Utils::keyMap($argNodes, function (ArgumentNode $arg) {
|
||||||
@ -158,11 +157,12 @@ class Values
|
|||||||
} else {
|
} else {
|
||||||
$valueNode = $argumentNode->value;
|
$valueNode = $argumentNode->value;
|
||||||
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
|
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
|
||||||
if ($coercedValue === $undefined) {
|
if (Utils::isInvalid($coercedValue)) {
|
||||||
$errors = DocumentValidator::isValidLiteralValue($argType, $valueNode);
|
// Note: ValuesOfCorrectType validation should catch this before
|
||||||
$message = !empty($errors) ? ("\n" . implode("\n", $errors)) : '';
|
// execution. This is a runtime check to ensure execution does not
|
||||||
|
// continue with an invalid argument value.
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Argument "' . $name . '" got invalid value ' . Printer::doPrint($valueNode) . '.' . $message,
|
'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.',
|
||||||
[ $argumentNode->value ]
|
[ $argumentNode->value ]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ namespace GraphQL\Utils;
|
|||||||
use GraphQL\Error\InvariantViolation;
|
use GraphQL\Error\InvariantViolation;
|
||||||
use GraphQL\Error\Warning;
|
use GraphQL\Error\Warning;
|
||||||
use GraphQL\Language\AST\FieldNode;
|
use GraphQL\Language\AST\FieldNode;
|
||||||
use GraphQL\Language\AST\ListType;
|
|
||||||
use GraphQL\Language\AST\ListTypeNode;
|
use GraphQL\Language\AST\ListTypeNode;
|
||||||
use GraphQL\Language\AST\NamedTypeNode;
|
use GraphQL\Language\AST\NamedTypeNode;
|
||||||
use GraphQL\Language\AST\Node;
|
use GraphQL\Language\AST\Node;
|
||||||
@ -20,7 +19,6 @@ use GraphQL\Type\Definition\InputObjectType;
|
|||||||
use GraphQL\Type\Definition\InputType;
|
use GraphQL\Type\Definition\InputType;
|
||||||
use GraphQL\Type\Definition\InterfaceType;
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
use GraphQL\Type\Definition\ListOfType;
|
use GraphQL\Type\Definition\ListOfType;
|
||||||
use GraphQL\Type\Definition\NonNull;
|
|
||||||
use GraphQL\Type\Definition\ObjectType;
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Definition\UnionType;
|
use GraphQL\Type\Definition\UnionType;
|
||||||
@ -217,14 +215,26 @@ class TypeInfo
|
|||||||
/**
|
/**
|
||||||
* TypeInfo constructor.
|
* TypeInfo constructor.
|
||||||
* @param Schema $schema
|
* @param Schema $schema
|
||||||
|
* @param Type|null $initialType
|
||||||
*/
|
*/
|
||||||
public function __construct(Schema $schema)
|
public function __construct(Schema $schema, $initialType = null)
|
||||||
{
|
{
|
||||||
$this->schema = $schema;
|
$this->schema = $schema;
|
||||||
$this->typeStack = [];
|
$this->typeStack = [];
|
||||||
$this->parentTypeStack = [];
|
$this->parentTypeStack = [];
|
||||||
$this->inputTypeStack = [];
|
$this->inputTypeStack = [];
|
||||||
$this->fieldDefStack = [];
|
$this->fieldDefStack = [];
|
||||||
|
if ($initialType) {
|
||||||
|
if (Type::isInputType($initialType)) {
|
||||||
|
$this->inputTypeStack[] = $initialType;
|
||||||
|
}
|
||||||
|
if (Type::isCompositeType($initialType)) {
|
||||||
|
$this->parentTypeStack[] = $initialType;
|
||||||
|
}
|
||||||
|
if (Type::isOutputType($initialType)) {
|
||||||
|
$this->typeStack[] = $initialType;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -239,7 +249,7 @@ class TypeInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Type
|
* @return CompositeType
|
||||||
*/
|
*/
|
||||||
function getParentType()
|
function getParentType()
|
||||||
{
|
{
|
||||||
@ -260,6 +270,17 @@ class TypeInfo
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return InputType|null
|
||||||
|
*/
|
||||||
|
public function getParentInputType()
|
||||||
|
{
|
||||||
|
$inputTypeStackLength = count($this->inputTypeStack);
|
||||||
|
if ($inputTypeStackLength > 1) {
|
||||||
|
return $this->inputTypeStack[$inputTypeStackLength - 2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FieldDefinition
|
* @return FieldDefinition
|
||||||
*/
|
*/
|
||||||
@ -369,10 +390,9 @@ class TypeInfo
|
|||||||
|
|
||||||
case NodeKind::LST:
|
case NodeKind::LST:
|
||||||
$listType = Type::getNullableType($this->getInputType());
|
$listType = Type::getNullableType($this->getInputType());
|
||||||
$itemType = null;
|
$itemType = $listType instanceof ListOfType
|
||||||
if ($itemType instanceof ListType) {
|
? $listType->getWrappedType()
|
||||||
$itemType = $listType->getWrappedType();
|
: $listType;
|
||||||
}
|
|
||||||
$this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null;
|
$this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -2,26 +2,13 @@
|
|||||||
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\DocumentNode;
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
use GraphQL\Language\AST\NodeKind;
|
|
||||||
use GraphQL\Language\AST\NullValueNode;
|
|
||||||
use GraphQL\Language\AST\VariableNode;
|
|
||||||
use GraphQL\Language\Printer;
|
|
||||||
use GraphQL\Language\Visitor;
|
use GraphQL\Language\Visitor;
|
||||||
use GraphQL\Type\Schema;
|
use GraphQL\Type\Schema;
|
||||||
use GraphQL\Type\Definition\EnumType;
|
|
||||||
use GraphQL\Type\Definition\InputObjectType;
|
|
||||||
use GraphQL\Type\Definition\ListOfType;
|
|
||||||
use GraphQL\Type\Definition\NonNull;
|
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Type\Definition\ScalarType;
|
|
||||||
use GraphQL\Utils\Utils;
|
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
use GraphQL\Validator\Rules\AbstractValidationRule;
|
use GraphQL\Validator\Rules\AbstractValidationRule;
|
||||||
use GraphQL\Validator\Rules\ArgumentsOfCorrectType;
|
use GraphQL\Validator\Rules\ValuesOfCorrectType;
|
||||||
use GraphQL\Validator\Rules\DefaultValuesOfCorrectType;
|
|
||||||
use GraphQL\Validator\Rules\DisableIntrospection;
|
use GraphQL\Validator\Rules\DisableIntrospection;
|
||||||
use GraphQL\Validator\Rules\ExecutableDefinitions;
|
use GraphQL\Validator\Rules\ExecutableDefinitions;
|
||||||
use GraphQL\Validator\Rules\FieldsOnCorrectType;
|
use GraphQL\Validator\Rules\FieldsOnCorrectType;
|
||||||
@ -48,6 +35,7 @@ use GraphQL\Validator\Rules\UniqueInputFieldNames;
|
|||||||
use GraphQL\Validator\Rules\UniqueOperationNames;
|
use GraphQL\Validator\Rules\UniqueOperationNames;
|
||||||
use GraphQL\Validator\Rules\UniqueVariableNames;
|
use GraphQL\Validator\Rules\UniqueVariableNames;
|
||||||
use GraphQL\Validator\Rules\VariablesAreInputTypes;
|
use GraphQL\Validator\Rules\VariablesAreInputTypes;
|
||||||
|
use GraphQL\Validator\Rules\VariablesDefaultValueAllowed;
|
||||||
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
use GraphQL\Validator\Rules\VariablesInAllowedPosition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,9 +132,9 @@ class DocumentValidator
|
|||||||
UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
|
UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(),
|
||||||
KnownArgumentNames::class => new KnownArgumentNames(),
|
KnownArgumentNames::class => new KnownArgumentNames(),
|
||||||
UniqueArgumentNames::class => new UniqueArgumentNames(),
|
UniqueArgumentNames::class => new UniqueArgumentNames(),
|
||||||
ArgumentsOfCorrectType::class => new ArgumentsOfCorrectType(),
|
ValuesOfCorrectType::class => new ValuesOfCorrectType(),
|
||||||
ProvidedNonNullArguments::class => new ProvidedNonNullArguments(),
|
ProvidedNonNullArguments::class => new ProvidedNonNullArguments(),
|
||||||
DefaultValuesOfCorrectType::class => new DefaultValuesOfCorrectType(),
|
VariablesDefaultValueAllowed::class => new VariablesDefaultValueAllowed(),
|
||||||
VariablesInAllowedPosition::class => new VariablesInAllowedPosition(),
|
VariablesInAllowedPosition::class => new VariablesInAllowedPosition(),
|
||||||
OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(),
|
OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(),
|
||||||
UniqueInputFieldNames::class => new UniqueInputFieldNames(),
|
UniqueInputFieldNames::class => new UniqueInputFieldNames(),
|
||||||
@ -226,121 +214,23 @@ class DocumentValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility for validators which determines if a value literal AST is valid given
|
* Utility which determines if a value literal node is valid for an input type.
|
||||||
* an input type.
|
|
||||||
*
|
*
|
||||||
* Note that this only validates literal values, variables are assumed to
|
* Deprecated. Rely on validation for documents containing literal values.
|
||||||
* provide values of the correct type.
|
|
||||||
*
|
*
|
||||||
* @return array
|
* @deprecated
|
||||||
|
* @return Error[]
|
||||||
*/
|
*/
|
||||||
public static function isValidLiteralValue(Type $type, $valueNode)
|
public static function isValidLiteralValue(Type $type, $valueNode)
|
||||||
{
|
{
|
||||||
// A value must be provided if the type is non-null.
|
$emptySchema = new Schema([]);
|
||||||
if ($type instanceof NonNull) {
|
$emptyDoc = new DocumentNode(['definitions' => []]);
|
||||||
if (!$valueNode || $valueNode instanceof NullValueNode) {
|
$typeInfo = new TypeInfo($emptySchema, $type);
|
||||||
return [ 'Expected "' . Utils::printSafe($type) . '", found null.' ];
|
$context = new ValidationContext($emptySchema, $emptyDoc, $typeInfo);
|
||||||
}
|
$validator = new ValuesOfCorrectType();
|
||||||
return static::isValidLiteralValue($type->getWrappedType(), $valueNode);
|
$visitor = $validator->getVisitor($context);
|
||||||
}
|
Visitor::visit($valueNode, Visitor::visitWithTypeInfo($typeInfo, $visitor));
|
||||||
|
return $context->getErrors();
|
||||||
if (!$valueNode || $valueNode instanceof NullValueNode) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function only tests literals, and assumes variables will provide
|
|
||||||
// values of the correct type.
|
|
||||||
if ($valueNode instanceof VariableNode) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lists accept a non-list value as a list of one.
|
|
||||||
if ($type instanceof ListOfType) {
|
|
||||||
$itemType = $type->getWrappedType();
|
|
||||||
if ($valueNode instanceof ListValueNode) {
|
|
||||||
$errors = [];
|
|
||||||
foreach($valueNode->values as $index => $itemNode) {
|
|
||||||
$tmp = static::isValidLiteralValue($itemType, $itemNode);
|
|
||||||
|
|
||||||
if ($tmp) {
|
|
||||||
$errors = array_merge($errors, Utils::map($tmp, function($error) use ($index) {
|
|
||||||
return "In element #$index: $error";
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
return static::isValidLiteralValue($itemType, $valueNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input objects check each defined field and look for undefined fields.
|
|
||||||
if ($type instanceof InputObjectType) {
|
|
||||||
if ($valueNode->kind !== NodeKind::OBJECT) {
|
|
||||||
return [ "Expected \"{$type->name}\", found not an object." ];
|
|
||||||
}
|
|
||||||
|
|
||||||
$fields = $type->getFields();
|
|
||||||
|
|
||||||
$errors = [];
|
|
||||||
|
|
||||||
// Ensure every provided field is defined.
|
|
||||||
$fieldNodes = $valueNode->fields;
|
|
||||||
|
|
||||||
foreach ($fieldNodes as $providedFieldNode) {
|
|
||||||
if (empty($fields[$providedFieldNode->name->value])) {
|
|
||||||
$errors[] = "In field \"{$providedFieldNode->name->value}\": Unknown field.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure every defined field is valid.
|
|
||||||
$fieldNodeMap = Utils::keyMap($fieldNodes, function($fieldNode) {return $fieldNode->name->value;});
|
|
||||||
foreach ($fields as $fieldName => $field) {
|
|
||||||
$result = static::isValidLiteralValue(
|
|
||||||
$field->getType(),
|
|
||||||
isset($fieldNodeMap[$fieldName]) ? $fieldNodeMap[$fieldName]->value : null
|
|
||||||
);
|
|
||||||
if ($result) {
|
|
||||||
$errors = array_merge($errors, Utils::map($result, function($error) use ($fieldName) {
|
|
||||||
return "In field \"$fieldName\": $error";
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($type instanceof EnumType) {
|
|
||||||
if (!$valueNode instanceof EnumValueNode || !$type->getValue($valueNode->value)) {
|
|
||||||
$printed = Printer::doPrint($valueNode);
|
|
||||||
return ["Expected type \"{$type->name}\", found $printed."];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($type instanceof ScalarType) {
|
|
||||||
// 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);
|
|
||||||
$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 [];
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Unknown type: ' . Utils::printSafe($type) . '.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace GraphQL\Validator\Rules;
|
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
|
||||||
use GraphQL\Language\AST\ArgumentNode;
|
|
||||||
use GraphQL\Language\AST\NodeKind;
|
|
||||||
use GraphQL\Language\Printer;
|
|
||||||
use GraphQL\Language\Visitor;
|
|
||||||
use GraphQL\Validator\DocumentValidator;
|
|
||||||
use GraphQL\Validator\ValidationContext;
|
|
||||||
|
|
||||||
class ArgumentsOfCorrectType extends AbstractValidationRule
|
|
||||||
{
|
|
||||||
static function badValueMessage($argName, $type, $value, $verboseErrors = [])
|
|
||||||
{
|
|
||||||
$message = $verboseErrors ? ("\n" . implode("\n", $verboseErrors)) : '';
|
|
||||||
return "Argument \"$argName\" has invalid value $value.$message";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
NodeKind::ARGUMENT => function(ArgumentNode $argNode) use ($context) {
|
|
||||||
$argDef = $context->getArgument();
|
|
||||||
if ($argDef) {
|
|
||||||
$errors = DocumentValidator::isValidLiteralValue($argDef->getType(), $argNode->value);
|
|
||||||
|
|
||||||
if (!empty($errors)) {
|
|
||||||
$context->reportError(new Error(
|
|
||||||
self::badValueMessage($argNode->name->value, $argDef->getType(), Printer::doPrint($argNode->value), $errors),
|
|
||||||
[$argNode->value]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Visitor::skipNode();
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace GraphQL\Validator\Rules;
|
|
||||||
|
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
|
||||||
use GraphQL\Language\AST\Node;
|
|
||||||
use GraphQL\Language\AST\NodeKind;
|
|
||||||
use GraphQL\Language\AST\VariableDefinitionNode;
|
|
||||||
use GraphQL\Language\Printer;
|
|
||||||
use GraphQL\Language\Visitor;
|
|
||||||
use GraphQL\Type\Definition\NonNull;
|
|
||||||
use GraphQL\Validator\DocumentValidator;
|
|
||||||
use GraphQL\Validator\ValidationContext;
|
|
||||||
|
|
||||||
class DefaultValuesOfCorrectType extends AbstractValidationRule
|
|
||||||
{
|
|
||||||
static function badValueForDefaultArgMessage($varName, $type, $value, $verboseErrors = null)
|
|
||||||
{
|
|
||||||
$message = $verboseErrors ? ("\n" . implode("\n", $verboseErrors)) : '';
|
|
||||||
return "Variable \$$varName has invalid default value: $value.$message";
|
|
||||||
}
|
|
||||||
|
|
||||||
static function defaultForNonNullArgMessage($varName, $type, $guessType)
|
|
||||||
{
|
|
||||||
return "Variable \$$varName of type $type " .
|
|
||||||
"is required and will never use the default value. " .
|
|
||||||
"Perhaps you meant to use type $guessType.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getVisitor(ValidationContext $context)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $varDefNode) use ($context) {
|
|
||||||
$name = $varDefNode->variable->name->value;
|
|
||||||
$defaultValue = $varDefNode->defaultValue;
|
|
||||||
$type = $context->getInputType();
|
|
||||||
|
|
||||||
if ($type instanceof NonNull && $defaultValue) {
|
|
||||||
$context->reportError(new Error(
|
|
||||||
static::defaultForNonNullArgMessage($name, $type, $type->getWrappedType()),
|
|
||||||
[$defaultValue]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if ($type && $defaultValue) {
|
|
||||||
$errors = DocumentValidator::isValidLiteralValue($type, $defaultValue);
|
|
||||||
if (!empty($errors)) {
|
|
||||||
$context->reportError(new Error(
|
|
||||||
static::badValueForDefaultArgMessage($name, $type, Printer::doPrint($defaultValue), $errors),
|
|
||||||
[$defaultValue]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Visitor::skipNode();
|
|
||||||
},
|
|
||||||
NodeKind::SELECTION_SET => function() {return Visitor::skipNode();},
|
|
||||||
NodeKind::FRAGMENT_DEFINITION => function() {return Visitor::skipNode();}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
226
src/Validator/Rules/ValuesOfCorrectType.php
Normal file
226
src/Validator/Rules/ValuesOfCorrectType.php
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Language\AST\BooleanValueNode;
|
||||||
|
use GraphQL\Language\AST\EnumValueNode;
|
||||||
|
use GraphQL\Language\AST\FloatValueNode;
|
||||||
|
use GraphQL\Language\AST\IntValueNode;
|
||||||
|
use GraphQL\Language\AST\ListValueNode;
|
||||||
|
use GraphQL\Language\AST\NodeKind;
|
||||||
|
use GraphQL\Language\AST\NullValueNode;
|
||||||
|
use GraphQL\Language\AST\ObjectFieldNode;
|
||||||
|
use GraphQL\Language\AST\ObjectValueNode;
|
||||||
|
use GraphQL\Language\AST\StringValueNode;
|
||||||
|
use GraphQL\Language\AST\ValueNode;
|
||||||
|
use GraphQL\Language\Printer;
|
||||||
|
use GraphQL\Language\Visitor;
|
||||||
|
use GraphQL\Type\Definition\EnumType;
|
||||||
|
use GraphQL\Type\Definition\EnumValueDefinition;
|
||||||
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
|
use GraphQL\Type\Definition\ListOfType;
|
||||||
|
use GraphQL\Type\Definition\NonNull;
|
||||||
|
use GraphQL\Type\Definition\ScalarType;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Utils\Utils;
|
||||||
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value literals of correct type
|
||||||
|
*
|
||||||
|
* A GraphQL document is only valid if all value literals are of the type
|
||||||
|
* expected at their position.
|
||||||
|
*/
|
||||||
|
class ValuesOfCorrectType extends AbstractValidationRule
|
||||||
|
{
|
||||||
|
static function badValueMessage($typeName, $valueName, $message = null)
|
||||||
|
{
|
||||||
|
return "Expected type {$typeName}, found {$valueName}" .
|
||||||
|
($message ? "; ${message}" : '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function requiredFieldMessage($typeName, $fieldName, $fieldTypeName)
|
||||||
|
{
|
||||||
|
return "Field {$typeName}.{$fieldName} of required type " .
|
||||||
|
"{$fieldTypeName} was not provided.";
|
||||||
|
}
|
||||||
|
|
||||||
|
static function unknownFieldMessage($typeName, $fieldName)
|
||||||
|
{
|
||||||
|
return "Field \"{$fieldName}\" is not defined by type {$typeName}.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVisitor(ValidationContext $context)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
NodeKind::NULL => function(NullValueNode $node) use ($context) {
|
||||||
|
$type = $context->getInputType();
|
||||||
|
if ($type instanceof NonNull) {
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::badValueMessage((string) $type, Printer::doPrint($node)),
|
||||||
|
$node
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NodeKind::LST => function(ListValueNode $node) use ($context) {
|
||||||
|
// Note: TypeInfo will traverse into a list's item type, so look to the
|
||||||
|
// parent input type to check if it is a list.
|
||||||
|
$type = Type::getNullableType($context->getParentInputType());
|
||||||
|
if (!$type instanceof ListOfType) {
|
||||||
|
$this->isValidScalar($context, $node);
|
||||||
|
return Visitor::skipNode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NodeKind::OBJECT => function(ObjectValueNode $node) use ($context) {
|
||||||
|
// Note: TypeInfo will traverse into a list's item type, so look to the
|
||||||
|
// parent input type to check if it is a list.
|
||||||
|
$type = Type::getNamedType($context->getInputType());
|
||||||
|
if (!$type instanceof InputObjectType) {
|
||||||
|
$this->isValidScalar($context, $node);
|
||||||
|
return Visitor::skipNode();
|
||||||
|
}
|
||||||
|
// Ensure every required field exists.
|
||||||
|
$inputFields = $type->getFields();
|
||||||
|
$nodeFields = iterator_to_array($node->fields);
|
||||||
|
$fieldNodeMap = array_combine(
|
||||||
|
array_map(function ($field) { return $field->name->value; }, $nodeFields),
|
||||||
|
array_values($nodeFields)
|
||||||
|
);
|
||||||
|
foreach ($inputFields as $fieldName => $fieldDef) {
|
||||||
|
$fieldType = $fieldDef->getType();
|
||||||
|
if (!isset($fieldNodeMap[$fieldName]) && $fieldType instanceof NonNull) {
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::requiredFieldMessage($type->name, $fieldName, (string) $fieldType),
|
||||||
|
$node
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NodeKind::OBJECT_FIELD => function(ObjectFieldNode $node) use ($context) {
|
||||||
|
$parentType = Type::getNamedType($context->getParentInputType());
|
||||||
|
$fieldType = $context->getInputType();
|
||||||
|
if (!$fieldType && $parentType) {
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::unknownFieldMessage($parentType->name, $node->name->value),
|
||||||
|
$node
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NodeKind::ENUM => function(EnumValueNode $node) use ($context) {
|
||||||
|
$type = Type::getNamedType($context->getInputType());
|
||||||
|
if (!$type instanceof EnumType) {
|
||||||
|
$this->isValidScalar($context, $node);
|
||||||
|
} else if (!$type->getValue($node->value)) {
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::badValueMessage(
|
||||||
|
$type->name,
|
||||||
|
Printer::doPrint($node),
|
||||||
|
$this->enumTypeSuggestion($type, $node)
|
||||||
|
),
|
||||||
|
$node
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NodeKind::INT => function (IntValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
|
||||||
|
NodeKind::FLOAT => function (FloatValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
|
||||||
|
NodeKind::STRING => function (StringValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
|
||||||
|
NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context) { $this->isValidScalar($context, $node); },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isValidScalar(ValidationContext $context, ValueNode $node)
|
||||||
|
{
|
||||||
|
// Report any error at the full type expected by the location.
|
||||||
|
$locationType = $context->getInputType();
|
||||||
|
|
||||||
|
if (!$locationType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = Type::getNamedType($locationType);
|
||||||
|
|
||||||
|
if (!$type instanceof ScalarType) {
|
||||||
|
$suggestions = $type instanceof EnumType
|
||||||
|
? $this->enumTypeSuggestion($type, $node)
|
||||||
|
: null;
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::badValueMessage(
|
||||||
|
(string) $locationType,
|
||||||
|
Printer::doPrint($node),
|
||||||
|
$suggestions
|
||||||
|
),
|
||||||
|
$node
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scalars determine if a literal value is valid via parseLiteral() which
|
||||||
|
// may throw or return an invalid value to indicate failure.
|
||||||
|
try {
|
||||||
|
$parseResult = $type->parseLiteral($node);
|
||||||
|
if (Utils::isInvalid($parseResult)) {
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::badValueMessage(
|
||||||
|
(string) $locationType,
|
||||||
|
Printer::doPrint($node)
|
||||||
|
),
|
||||||
|
$node
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (\Exception $error) {
|
||||||
|
// Ensure a reference to the original error is maintained.
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::badValueMessage(
|
||||||
|
(string) $locationType,
|
||||||
|
Printer::doPrint($node),
|
||||||
|
$error->getMessage()
|
||||||
|
),
|
||||||
|
$node,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
$error
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (\Throwable $error) {
|
||||||
|
// Ensure a reference to the original error is maintained.
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::badValueMessage(
|
||||||
|
(string) $locationType,
|
||||||
|
Printer::doPrint($node),
|
||||||
|
$error->getMessage()
|
||||||
|
),
|
||||||
|
$node,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
$error
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function enumTypeSuggestion(EnumType $type, ValueNode $node)
|
||||||
|
{
|
||||||
|
$suggestions = Utils::suggestionList(
|
||||||
|
Printer::doPrint($node),
|
||||||
|
array_map(function (EnumValueDefinition $value) { return $value->name; }, $type->getValues())
|
||||||
|
);
|
||||||
|
|
||||||
|
return $suggestions ? 'Did you mean the enum value: ' . Utils::orList($suggestions) . '?' : '';
|
||||||
|
}
|
||||||
|
}
|
60
src/Validator/Rules/VariablesDefaultValueAllowed.php
Normal file
60
src/Validator/Rules/VariablesDefaultValueAllowed.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Validator\Rules;
|
||||||
|
|
||||||
|
use GraphQL\Error\Error;
|
||||||
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\NodeKind;
|
||||||
|
use GraphQL\Language\AST\SelectionSetNode;
|
||||||
|
use GraphQL\Language\AST\VariableDefinitionNode;
|
||||||
|
use GraphQL\Language\Visitor;
|
||||||
|
use GraphQL\Type\Definition\NonNull;
|
||||||
|
use GraphQL\Validator\ValidationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable's default value is allowed
|
||||||
|
*
|
||||||
|
* A GraphQL document is only valid if all variable default values are allowed
|
||||||
|
* due to a variable not being required.
|
||||||
|
*/
|
||||||
|
class VariablesDefaultValueAllowed extends AbstractValidationRule
|
||||||
|
{
|
||||||
|
static function defaultForRequiredVarMessage($varName, $type, $guessType)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
"Variable \"\${$varName}\" of type \"{$type}\" is required and " .
|
||||||
|
'will not use the default value. ' .
|
||||||
|
"Perhaps you meant to use type \"{$guessType}\"."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVisitor(ValidationContext $context)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $node) use ($context) {
|
||||||
|
$name = $node->variable->name->value;
|
||||||
|
$defaultValue = $node->defaultValue;
|
||||||
|
$type = $context->getInputType();
|
||||||
|
if ($type instanceof NonNull && $defaultValue) {
|
||||||
|
$context->reportError(
|
||||||
|
new Error(
|
||||||
|
self::defaultForRequiredVarMessage(
|
||||||
|
$name,
|
||||||
|
$type,
|
||||||
|
$type->getWrappedType()
|
||||||
|
),
|
||||||
|
[$defaultValue]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Visitor::skipNode();
|
||||||
|
},
|
||||||
|
NodeKind::SELECTION_SET => function(SelectionSetNode $node) use ($context) {
|
||||||
|
return Visitor::skipNode();
|
||||||
|
},
|
||||||
|
NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) use ($context) {
|
||||||
|
return Visitor::skipNode();
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -12,11 +12,9 @@ use GraphQL\Error\Error;
|
|||||||
use GraphQL\Type\Schema;
|
use GraphQL\Type\Schema;
|
||||||
use GraphQL\Language\AST\DocumentNode;
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
use GraphQL\Language\AST\FragmentDefinitionNode;
|
use GraphQL\Language\AST\FragmentDefinitionNode;
|
||||||
use GraphQL\Language\AST\Node;
|
|
||||||
use GraphQL\Type\Definition\CompositeType;
|
use GraphQL\Type\Definition\CompositeType;
|
||||||
use GraphQL\Type\Definition\FieldDefinition;
|
use GraphQL\Type\Definition\FieldDefinition;
|
||||||
use GraphQL\Type\Definition\InputType;
|
use GraphQL\Type\Definition\InputType;
|
||||||
use GraphQL\Type\Definition\OutputType;
|
|
||||||
use GraphQL\Type\Definition\Type;
|
use GraphQL\Type\Definition\Type;
|
||||||
use GraphQL\Utils\TypeInfo;
|
use GraphQL\Utils\TypeInfo;
|
||||||
|
|
||||||
@ -275,6 +273,14 @@ class ValidationContext
|
|||||||
return $this->typeInfo->getInputType();
|
return $this->typeInfo->getInputType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return InputType
|
||||||
|
*/
|
||||||
|
function getParentInputType()
|
||||||
|
{
|
||||||
|
return $this->typeInfo->getParentInputType();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FieldDefinition
|
* @return FieldDefinition
|
||||||
*/
|
*/
|
||||||
|
@ -4,7 +4,6 @@ namespace GraphQL\Tests\Executor;
|
|||||||
require_once __DIR__ . '/TestClasses.php';
|
require_once __DIR__ . '/TestClasses.php';
|
||||||
|
|
||||||
use GraphQL\Error\Error;
|
use GraphQL\Error\Error;
|
||||||
use GraphQL\Error\InvariantViolation;
|
|
||||||
use GraphQL\Executor\Executor;
|
use GraphQL\Executor\Executor;
|
||||||
use GraphQL\Language\Parser;
|
use GraphQL\Language\Parser;
|
||||||
use GraphQL\Type\Schema;
|
use GraphQL\Type\Schema;
|
||||||
@ -82,9 +81,9 @@ class VariablesTest extends \PHPUnit_Framework_TestCase
|
|||||||
$expected = [
|
$expected = [
|
||||||
'data' => ['fieldWithObjectInput' => null],
|
'data' => ['fieldWithObjectInput' => null],
|
||||||
'errors' => [[
|
'errors' => [[
|
||||||
'message' => 'Argument "input" got invalid value ["foo", "bar", "baz"].' . "\n" .
|
'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].',
|
||||||
'Expected "TestInputObject", found not an object.',
|
'path' => ['fieldWithObjectInput'],
|
||||||
'path' => ['fieldWithObjectInput']
|
'locations' => [['line' => 3, 'column' => 39]]
|
||||||
]]
|
]]
|
||||||
];
|
];
|
||||||
$this->assertArraySubset($expected, $result);
|
$this->assertArraySubset($expected, $result);
|
||||||
@ -877,8 +876,7 @@ class VariablesTest extends \PHPUnit_Framework_TestCase
|
|||||||
'data' => ['fieldWithDefaultArgumentValue' => null],
|
'data' => ['fieldWithDefaultArgumentValue' => null],
|
||||||
'errors' => [[
|
'errors' => [[
|
||||||
'message' =>
|
'message' =>
|
||||||
'Argument "input" got invalid value WRONG_TYPE.' . "\n" .
|
'Argument "input" has invalid value WRONG_TYPE.',
|
||||||
'Expected type "String", found WRONG_TYPE.',
|
|
||||||
'locations' => [ [ 'line' => 2, 'column' => 50 ] ],
|
'locations' => [ [ 'line' => 2, 'column' => 50 ] ],
|
||||||
'path' => [ 'fieldWithDefaultArgumentValue' ],
|
'path' => [ 'fieldWithDefaultArgumentValue' ],
|
||||||
'category' => 'graphql',
|
'category' => 'graphql',
|
||||||
|
@ -220,7 +220,22 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase
|
|||||||
'{ colorEnum(fromEnum: "GREEN") }',
|
'{ colorEnum(fromEnum: "GREEN") }',
|
||||||
null,
|
null,
|
||||||
[
|
[
|
||||||
'message' => "Argument \"fromEnum\" has invalid value \"GREEN\".\nExpected type \"Color\", found \"GREEN\".",
|
'message' => "Expected type Color, found \"GREEN\"; Did you mean the enum value: GREEN?",
|
||||||
|
'locations' => [new SourceLocation(1, 23)]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it does not accept valuesNotInTheEnum
|
||||||
|
*/
|
||||||
|
public function testDoesNotAcceptValuesNotInTheEnum()
|
||||||
|
{
|
||||||
|
$this->expectFailure(
|
||||||
|
'{ colorEnum(fromEnum: GREENISH) }',
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'message' => "Expected type Color, found GREENISH; Did you mean the enum value: GREEN?",
|
||||||
'locations' => [new SourceLocation(1, 23)]
|
'locations' => [new SourceLocation(1, 23)]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@ -236,7 +251,8 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase
|
|||||||
null,
|
null,
|
||||||
[
|
[
|
||||||
'message' => 'Expected a value of type "Color" but received: GREEN',
|
'message' => 'Expected a value of type "Color" but received: GREEN',
|
||||||
'locations' => [new SourceLocation(1, 3)]
|
'locations' => [new SourceLocation(1, 3)],
|
||||||
|
'path' => ['colorEnum'],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -249,7 +265,7 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->expectFailure(
|
$this->expectFailure(
|
||||||
'{ colorEnum(fromEnum: 1) }',
|
'{ colorEnum(fromEnum: 1) }',
|
||||||
null,
|
null,
|
||||||
"Argument \"fromEnum\" has invalid value 1.\nExpected type \"Color\", found 1."
|
"Expected type Color, found 1."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +277,7 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->expectFailure(
|
$this->expectFailure(
|
||||||
'{ colorEnum(fromInt: GREEN) }',
|
'{ colorEnum(fromInt: GREEN) }',
|
||||||
null,
|
null,
|
||||||
"Argument \"fromInt\" has invalid value GREEN.\nExpected type \"Int\", found GREEN."
|
"Expected type Int, found GREEN."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
37
tests/Utils/IsValidLiteralValueTest.php
Normal file
37
tests/Utils/IsValidLiteralValueTest.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Utils;
|
||||||
|
|
||||||
|
use GraphQL\Language\Parser;
|
||||||
|
use GraphQL\Language\SourceLocation;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
use GraphQL\Utils\Utils;
|
||||||
|
use GraphQL\Validator\DocumentValidator;
|
||||||
|
|
||||||
|
class IsValidLiteralValueTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
// DESCRIBE: isValidLiteralValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Returns no errors for a valid value
|
||||||
|
*/
|
||||||
|
public function testReturnsNoErrorsForAValidValue()
|
||||||
|
{
|
||||||
|
$this->assertEquals(
|
||||||
|
[],
|
||||||
|
DocumentValidator::isValidLiteralValue(Type::int(), Parser::parseValue('123'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it Returns errors for an invalid value
|
||||||
|
*/
|
||||||
|
public function testReturnsErrorsForForInvalidValue()
|
||||||
|
{
|
||||||
|
$errors = DocumentValidator::isValidLiteralValue(Type::int(), Parser::parseValue('"abc"'));
|
||||||
|
|
||||||
|
$this->assertCount(1, $errors);
|
||||||
|
$this->assertEquals('Expected type Int, found "abc".', $errors[0]->getMessage());
|
||||||
|
$this->assertEquals([new SourceLocation(1, 1)], $errors[0]->getLocations());
|
||||||
|
$this->assertEquals(null, $errors[0]->getPath());
|
||||||
|
}
|
||||||
|
}
|
@ -1,188 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace GraphQL\Tests\Validator;
|
|
||||||
|
|
||||||
use GraphQL\Error\FormattedError;
|
|
||||||
use GraphQL\Language\SourceLocation;
|
|
||||||
use GraphQL\Validator\Rules\DefaultValuesOfCorrectType;
|
|
||||||
|
|
||||||
class DefaultValuesOfCorrectTypeTest extends TestCase
|
|
||||||
{
|
|
||||||
// Validate: Variable default values of correct type
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @it variables with no default values
|
|
||||||
*/
|
|
||||||
public function testVariablesWithNoDefaultValues()
|
|
||||||
{
|
|
||||||
$this->expectPassesRule(new DefaultValuesOfCorrectType, '
|
|
||||||
query NullableValues($a: Int, $b: String, $c: ComplexInput) {
|
|
||||||
dog { name }
|
|
||||||
}
|
|
||||||
');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @it required variables without default values
|
|
||||||
*/
|
|
||||||
public function testRequiredVariablesWithoutDefaultValues()
|
|
||||||
{
|
|
||||||
$this->expectPassesRule(new DefaultValuesOfCorrectType, '
|
|
||||||
query RequiredValues($a: Int!, $b: String!) {
|
|
||||||
dog { name }
|
|
||||||
}
|
|
||||||
');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @it variables with valid default values
|
|
||||||
*/
|
|
||||||
public function testVariablesWithValidDefaultValues()
|
|
||||||
{
|
|
||||||
$this->expectPassesRule(new DefaultValuesOfCorrectType, '
|
|
||||||
query WithDefaultValues(
|
|
||||||
$a: Int = 1,
|
|
||||||
$b: String = "ok",
|
|
||||||
$c: ComplexInput = { requiredField: true, intField: 3 }
|
|
||||||
) {
|
|
||||||
dog { name }
|
|
||||||
}
|
|
||||||
');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @it variables with valid default null values
|
|
||||||
*/
|
|
||||||
public function testVariablesWithValidDefaultNullValues()
|
|
||||||
{
|
|
||||||
$this->expectPassesRule(new DefaultValuesOfCorrectType(), '
|
|
||||||
query WithDefaultValues(
|
|
||||||
$a: Int = null,
|
|
||||||
$b: String = null,
|
|
||||||
$c: ComplexInput = { requiredField: true, intField: null }
|
|
||||||
) {
|
|
||||||
dog { name }
|
|
||||||
}
|
|
||||||
');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @it variables with invalid default null values
|
|
||||||
*/
|
|
||||||
public function testVariablesWithInvalidDefaultNullValues()
|
|
||||||
{
|
|
||||||
$this->expectFailsRule(new DefaultValuesOfCorrectType(), '
|
|
||||||
query WithDefaultValues(
|
|
||||||
$a: Int! = null,
|
|
||||||
$b: String! = null,
|
|
||||||
$c: ComplexInput = { requiredField: null, intField: null }
|
|
||||||
) {
|
|
||||||
dog { name }
|
|
||||||
}
|
|
||||||
', [
|
|
||||||
$this->defaultForNonNullArg('a', 'Int!', 'Int', 3, 20),
|
|
||||||
$this->badValue('a', 'Int!', 'null', 3, 20, [
|
|
||||||
'Expected "Int!", found null.'
|
|
||||||
]),
|
|
||||||
$this->defaultForNonNullArg('b', 'String!', 'String', 4, 23),
|
|
||||||
$this->badValue('b', 'String!', 'null', 4, 23, [
|
|
||||||
'Expected "String!", found null.'
|
|
||||||
]),
|
|
||||||
$this->badValue('c', 'ComplexInput', '{requiredField: null, intField: null}',
|
|
||||||
5, 28, [
|
|
||||||
'In field "requiredField": Expected "Boolean!", found null.'
|
|
||||||
]
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @it no required variables with default values
|
|
||||||
*/
|
|
||||||
public function testNoRequiredVariablesWithDefaultValues()
|
|
||||||
{
|
|
||||||
$this->expectFailsRule(new DefaultValuesOfCorrectType, '
|
|
||||||
query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") {
|
|
||||||
dog { name }
|
|
||||||
}
|
|
||||||
', [
|
|
||||||
$this->defaultForNonNullArg('a', 'Int!', 'Int', 2, 49),
|
|
||||||
$this->defaultForNonNullArg('b', 'String!', 'String', 2, 66)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @it variables with invalid default values
|
|
||||||
*/
|
|
||||||
public function testVariablesWithInvalidDefaultValues()
|
|
||||||
{
|
|
||||||
$this->expectFailsRule(new DefaultValuesOfCorrectType, '
|
|
||||||
query InvalidDefaultValues(
|
|
||||||
$a: Int = "one",
|
|
||||||
$b: String = 4,
|
|
||||||
$c: ComplexInput = "notverycomplex"
|
|
||||||
) {
|
|
||||||
dog { name }
|
|
||||||
}
|
|
||||||
', [
|
|
||||||
$this->badValue('a', 'Int', '"one"', 3, 19, [
|
|
||||||
'Expected type "Int", found "one".'
|
|
||||||
]),
|
|
||||||
$this->badValue('b', 'String', '4', 4, 22, [
|
|
||||||
'Expected type "String", found 4.'
|
|
||||||
]),
|
|
||||||
$this->badValue('c', 'ComplexInput', '"notverycomplex"', 5, 28, [
|
|
||||||
'Expected "ComplexInput", found not an object.'
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @it complex variables missing required field
|
|
||||||
*/
|
|
||||||
public function testComplexVariablesMissingRequiredField()
|
|
||||||
{
|
|
||||||
$this->expectFailsRule(new DefaultValuesOfCorrectType, '
|
|
||||||
query MissingRequiredField($a: ComplexInput = {intField: 3}) {
|
|
||||||
dog { name }
|
|
||||||
}
|
|
||||||
', [
|
|
||||||
$this->badValue('a', 'ComplexInput', '{intField: 3}', 2, 53, [
|
|
||||||
'In field "requiredField": Expected "Boolean!", found null.'
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @it list variables with invalid item
|
|
||||||
*/
|
|
||||||
public function testListVariablesWithInvalidItem()
|
|
||||||
{
|
|
||||||
$this->expectFailsRule(new DefaultValuesOfCorrectType, '
|
|
||||||
query InvalidItem($a: [String] = ["one", 2]) {
|
|
||||||
dog { name }
|
|
||||||
}
|
|
||||||
', [
|
|
||||||
$this->badValue('a', '[String]', '["one", 2]', 2, 40, [
|
|
||||||
'In element #1: Expected type "String", found 2.'
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function defaultForNonNullArg($varName, $typeName, $guessTypeName, $line, $column)
|
|
||||||
{
|
|
||||||
return FormattedError::create(
|
|
||||||
DefaultValuesOfCorrectType::defaultForNonNullArgMessage($varName, $typeName, $guessTypeName),
|
|
||||||
[ new SourceLocation($line, $column) ]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function badValue($varName, $typeName, $val, $line, $column, $errors = null)
|
|
||||||
{
|
|
||||||
$realErrors = !$errors ? ["Expected type \"$typeName\", found $val."] : $errors;
|
|
||||||
|
|
||||||
return FormattedError::create(
|
|
||||||
DefaultValuesOfCorrectType::badValueForDefaultArgMessage($varName, $typeName, $val, $realErrors),
|
|
||||||
[ new SourceLocation($line, $column) ]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -260,13 +260,6 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$anyScalar = new CustomScalarType([
|
|
||||||
'name' => 'Any',
|
|
||||||
'serialize' => function ($value) { return $value; },
|
|
||||||
'parseLiteral' => function ($node) { return $node; }, // Allows any value
|
|
||||||
'parseValue' => function ($value) { return $value; }, // Allows any value
|
|
||||||
]);
|
|
||||||
|
|
||||||
$invalidScalar = new CustomScalarType([
|
$invalidScalar = new CustomScalarType([
|
||||||
'name' => 'Invalid',
|
'name' => 'Invalid',
|
||||||
'serialize' => function ($value) {
|
'serialize' => function ($value) {
|
||||||
@ -280,6 +273,13 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$anyScalar = new CustomScalarType([
|
||||||
|
'name' => 'Any',
|
||||||
|
'serialize' => function ($value) { return $value; },
|
||||||
|
'parseLiteral' => function ($node) { return $node; }, // Allows any value
|
||||||
|
'parseValue' => function ($value) { return $value; }, // Allows any value
|
||||||
|
]);
|
||||||
|
|
||||||
$queryRoot = new ObjectType([
|
$queryRoot = new ObjectType([
|
||||||
'name' => 'QueryRoot',
|
'name' => 'QueryRoot',
|
||||||
'fields' => [
|
'fields' => [
|
||||||
@ -295,16 +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],
|
||||||
'anyArg' => [
|
|
||||||
'args' => ['arg' => ['type' => $anyScalar]],
|
|
||||||
'type' => Type::string(),
|
|
||||||
],
|
|
||||||
'invalidArg' => [
|
'invalidArg' => [
|
||||||
'args' => [
|
'args' => [
|
||||||
'arg' => ['type' => $invalidScalar]
|
'arg' => ['type' => $invalidScalar]
|
||||||
],
|
],
|
||||||
'type' => Type::string(),
|
'type' => Type::string(),
|
||||||
]
|
],
|
||||||
|
'anyArg' => [
|
||||||
|
'args' => ['arg' => ['type' => $anyScalar]],
|
||||||
|
'type' => Type::string(),
|
||||||
|
],
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace GraphQL\Tests\Validator;
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
use GraphQL\Error\FormattedError;
|
|
||||||
use GraphQL\Validator\DocumentValidator;
|
|
||||||
use GraphQL\Validator\Rules\QueryComplexity;
|
|
||||||
|
|
||||||
class ValidationTest extends TestCase
|
class ValidationTest extends TestCase
|
||||||
{
|
{
|
||||||
// Validate: Supports full validation
|
// Validate: Supports full validation
|
||||||
@ -40,8 +36,7 @@ class ValidationTest extends TestCase
|
|||||||
';
|
';
|
||||||
|
|
||||||
$expectedError = [
|
$expectedError = [
|
||||||
'message' => "Argument \"arg\" has invalid value \"bad value\".
|
'message' => "Expected type Invalid, found \"bad value\"; Invalid scalar is always invalid: bad value",
|
||||||
Expected type \"Invalid\", found \"bad value\"; Invalid scalar is always invalid: bad value",
|
|
||||||
'locations' => [ ['line' => 3, 'column' => 25] ]
|
'locations' => [ ['line' => 3, 'column' => 25] ]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
109
tests/Validator/VariablesDefaultValueAllowedTest.php
Normal file
109
tests/Validator/VariablesDefaultValueAllowedTest.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Tests\Validator;
|
||||||
|
|
||||||
|
use GraphQL\Error\FormattedError;
|
||||||
|
use GraphQL\Language\SourceLocation;
|
||||||
|
use GraphQL\Validator\Rules\VariablesDefaultValueAllowed;
|
||||||
|
|
||||||
|
class VariablesDefaultValueAllowedTest extends TestCase
|
||||||
|
{
|
||||||
|
private function defaultForRequiredVar($varName, $typeName, $guessTypeName, $line, $column)
|
||||||
|
{
|
||||||
|
return FormattedError::create(
|
||||||
|
VariablesDefaultValueAllowed::defaultForRequiredVarMessage(
|
||||||
|
$varName,
|
||||||
|
$typeName,
|
||||||
|
$guessTypeName
|
||||||
|
),
|
||||||
|
[new SourceLocation($line, $column)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DESCRIBE: Validate: Variable default value is allowed
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it variables with no default values
|
||||||
|
*/
|
||||||
|
public function testVariablesWithNoDefaultValues()
|
||||||
|
{
|
||||||
|
$this->expectPassesRule(new VariablesDefaultValueAllowed(), '
|
||||||
|
query NullableValues($a: Int, $b: String, $c: ComplexInput) {
|
||||||
|
dog { name }
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it required variables without default values
|
||||||
|
*/
|
||||||
|
public function testRequiredVariablesWithoutDefaultValues()
|
||||||
|
{
|
||||||
|
$this->expectPassesRule(new VariablesDefaultValueAllowed(), '
|
||||||
|
query RequiredValues($a: Int!, $b: String!) {
|
||||||
|
dog { name }
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it variables with valid default values
|
||||||
|
*/
|
||||||
|
public function testVariablesWithValidDefaultValues()
|
||||||
|
{
|
||||||
|
$this->expectPassesRule(new VariablesDefaultValueAllowed(), '
|
||||||
|
query WithDefaultValues(
|
||||||
|
$a: Int = 1,
|
||||||
|
$b: String = "ok",
|
||||||
|
$c: ComplexInput = { requiredField: true, intField: 3 }
|
||||||
|
) {
|
||||||
|
dog { name }
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it variables with valid default null values
|
||||||
|
*/
|
||||||
|
public function testVariablesWithValidDefaultNullValues()
|
||||||
|
{
|
||||||
|
$this->expectPassesRule(new VariablesDefaultValueAllowed(), '
|
||||||
|
query WithDefaultValues(
|
||||||
|
$a: Int = null,
|
||||||
|
$b: String = null,
|
||||||
|
$c: ComplexInput = { requiredField: true, intField: null }
|
||||||
|
) {
|
||||||
|
dog { name }
|
||||||
|
}
|
||||||
|
');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it no required variables with default values
|
||||||
|
*/
|
||||||
|
public function testNoRequiredVariablesWithDefaultValues()
|
||||||
|
{
|
||||||
|
$this->expectFailsRule(new VariablesDefaultValueAllowed(), '
|
||||||
|
query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") {
|
||||||
|
dog { name }
|
||||||
|
}
|
||||||
|
', [
|
||||||
|
$this->defaultForRequiredVar('a', 'Int!', 'Int', 2, 49),
|
||||||
|
$this->defaultForRequiredVar('b', 'String!', 'String', 2, 66),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @it variables with invalid default null values
|
||||||
|
*/
|
||||||
|
public function testNullIntoNullableType()
|
||||||
|
{
|
||||||
|
$this->expectFailsRule(new VariablesDefaultValueAllowed(), '
|
||||||
|
query WithDefaultValues($a: Int! = null, $b: String! = null) {
|
||||||
|
dog { name }
|
||||||
|
}
|
||||||
|
', [
|
||||||
|
$this->defaultForRequiredVar('a', 'Int!', 'Int', 2, 42),
|
||||||
|
$this->defaultForRequiredVar('b', 'String!', 'String', 2, 62),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user