mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 12:56:05 +03:00
Support for NullValue
This commit is contained in:
parent
9bf8e82d7c
commit
8a676cde99
@ -5,17 +5,16 @@ namespace GraphQL\Executor;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Language\AST\Argument;
|
||||
use GraphQL\Language\AST\NullValue;
|
||||
use GraphQL\Language\AST\VariableDefinition;
|
||||
use GraphQL\Language\Printer;
|
||||
use GraphQL\Schema;
|
||||
use GraphQL\Type\Definition\EnumType;
|
||||
use GraphQL\Type\Definition\FieldArgument;
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\InputType;
|
||||
use GraphQL\Type\Definition\LeafType;
|
||||
use GraphQL\Type\Definition\ListOfType;
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Utils;
|
||||
|
||||
@ -65,16 +64,29 @@ class Values
|
||||
$valueAST = isset($argASTMap[$name]) ? $argASTMap[$name]->value : null;
|
||||
$value = Utils\AST::valueFromAST($valueAST, $argDef->getType(), $variableValues);
|
||||
|
||||
if (null === $value && null === $argDef->defaultValue && !$argDef->defaultValueExists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
$value = $argDef->defaultValue;
|
||||
}
|
||||
if (null !== $value) {
|
||||
$result[$name] = $value;
|
||||
if (NullValue::getNullValue() === $value) {
|
||||
$value = null;
|
||||
}
|
||||
$result[$name] = $value;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Moved to Utils\AST::valueFromAST
|
||||
*
|
||||
* @param $valueAST
|
||||
* @param InputType $type
|
||||
* @param null $variables
|
||||
* @return array|null|\stdClass
|
||||
*/
|
||||
public static function valueFromAST($valueAST, InputType $type, $variables = null)
|
||||
{
|
||||
return Utils\AST::valueFromAST($valueAST, $type, $variables);
|
||||
@ -105,7 +117,8 @@ class Values
|
||||
if (null === $input) {
|
||||
$defaultValue = $definitionAST->defaultValue;
|
||||
if ($defaultValue) {
|
||||
return Utils\AST::valueFromAST($defaultValue, $inputType);
|
||||
$value = Utils\AST::valueFromAST($defaultValue, $inputType);
|
||||
return $value === NullValue::getNullValue() ? null : $value;
|
||||
}
|
||||
}
|
||||
return self::coerceValue($inputType, $input);
|
||||
|
@ -31,6 +31,7 @@ class NodeType
|
||||
const STRING = 'StringValue';
|
||||
const BOOLEAN = 'BooleanValue';
|
||||
const ENUM = 'EnumValue';
|
||||
const NULL = 'NullValue';
|
||||
const LST = 'ListValue';
|
||||
const OBJECT = 'ObjectValue';
|
||||
const OBJECT_FIELD = 'ObjectField';
|
||||
|
13
src/Language/AST/NullValue.php
Normal file
13
src/Language/AST/NullValue.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace GraphQL\Language\AST;
|
||||
|
||||
class NullValue extends Node implements Value
|
||||
{
|
||||
public $kind = NodeType::NULL;
|
||||
|
||||
public static function getNullValue()
|
||||
{
|
||||
static $nullValue;
|
||||
return $nullValue ?: $nullValue = new \stdClass();
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ use GraphQL\Language\AST\Location;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\NamedType;
|
||||
use GraphQL\Language\AST\NonNullType;
|
||||
use GraphQL\Language\AST\NullValue;
|
||||
use GraphQL\Language\AST\ObjectField;
|
||||
use GraphQL\Language\AST\ObjectTypeDefinition;
|
||||
use GraphQL\Language\AST\ObjectValue;
|
||||
@ -610,16 +611,19 @@ class Parser
|
||||
* - FloatValue
|
||||
* - StringValue
|
||||
* - BooleanValue
|
||||
* - NullValue
|
||||
* - EnumValue
|
||||
* - ListValue[?Const]
|
||||
* - ObjectValue[?Const]
|
||||
*
|
||||
* BooleanValue : one of `true` `false`
|
||||
*
|
||||
* NullValue : `null`
|
||||
*
|
||||
* EnumValue : Name but not `true`, `false` or `null`
|
||||
*
|
||||
* @param $isConst
|
||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable|ListValue|ObjectValue
|
||||
* @return BooleanValue|EnumValue|FloatValue|IntValue|StringValue|Variable|ListValue|ObjectValue|NullValue
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
function parseValueLiteral($isConst)
|
||||
@ -655,7 +659,12 @@ class Parser
|
||||
'value' => $token->value === 'true',
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
} else if ($token->value !== 'null') {
|
||||
} else if ($token->value === 'null') {
|
||||
$this->lexer->advance();
|
||||
return new NullValue([
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
} else {
|
||||
$this->lexer->advance();
|
||||
return new EnumValue([
|
||||
'value' => $token->value,
|
||||
|
@ -21,11 +21,11 @@ use GraphQL\Language\AST\FragmentSpread;
|
||||
use GraphQL\Language\AST\InlineFragment;
|
||||
use GraphQL\Language\AST\IntValue;
|
||||
use GraphQL\Language\AST\ListType;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\NamedType;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeType;
|
||||
use GraphQL\Language\AST\NonNullType;
|
||||
use GraphQL\Language\AST\NullValue;
|
||||
use GraphQL\Language\AST\ObjectField;
|
||||
use GraphQL\Language\AST\ObjectTypeDefinition;
|
||||
use GraphQL\Language\AST\ObjectValue;
|
||||
@ -112,14 +112,33 @@ class Printer
|
||||
},
|
||||
|
||||
// Value
|
||||
NodeType::INT => function(IntValue $node) {return $node->value;},
|
||||
NodeType::FLOAT => function(FloatValue $node) {return $node->value;},
|
||||
NodeType::STRING => function(StringValue $node) {return json_encode($node->value);},
|
||||
NodeType::BOOLEAN => function(BooleanValue $node) {return $node->value ? 'true' : 'false';},
|
||||
NodeType::ENUM => function(EnumValue $node) {return $node->value;},
|
||||
NodeType::LST => function(ListValue $node) {return '[' . $this->join($node->values, ', ') . ']';},
|
||||
NodeType::OBJECT => function(ObjectValue $node) {return '{' . $this->join($node->fields, ', ') . '}';},
|
||||
NodeType::OBJECT_FIELD => function(ObjectField $node) {return $node->name . ': ' . $node->value;},
|
||||
NodeType::INT => function(IntValue $node) {
|
||||
return $node->value;
|
||||
},
|
||||
NodeType::FLOAT => function(FloatValue $node) {
|
||||
return $node->value;
|
||||
},
|
||||
NodeType::STRING => function(StringValue $node) {
|
||||
return json_encode($node->value);
|
||||
},
|
||||
NodeType::BOOLEAN => function(BooleanValue $node) {
|
||||
return $node->value ? 'true' : 'false';
|
||||
},
|
||||
NodeType::NULL => function(NullValue $node) {
|
||||
return 'null';
|
||||
},
|
||||
NodeType::ENUM => function(EnumValue $node) {
|
||||
return $node->value;
|
||||
},
|
||||
NodeType::LST => function(ListValue $node) {
|
||||
return '[' . $this->join($node->values, ', ') . ']';
|
||||
},
|
||||
NodeType::OBJECT => function(ObjectValue $node) {
|
||||
return '{' . $this->join($node->fields, ', ') . '}';
|
||||
},
|
||||
NodeType::OBJECT_FIELD => function(ObjectField $node) {
|
||||
return $node->name . ': ' . $node->value;
|
||||
},
|
||||
|
||||
// Directive
|
||||
NodeType::DIRECTIVE => function(Directive $node) {
|
||||
@ -127,9 +146,15 @@ class Printer
|
||||
},
|
||||
|
||||
// Type
|
||||
NodeType::NAMED_TYPE => function(NamedType $node) {return $node->name;},
|
||||
NodeType::LIST_TYPE => function(ListType $node) {return '[' . $node->type . ']';},
|
||||
NodeType::NON_NULL_TYPE => function(NonNullType $node) {return $node->type . '!';},
|
||||
NodeType::NAMED_TYPE => function(NamedType $node) {
|
||||
return $node->name;
|
||||
},
|
||||
NodeType::LIST_TYPE => function(ListType $node) {
|
||||
return '[' . $node->type . ']';
|
||||
},
|
||||
NodeType::NON_NULL_TYPE => function(NonNullType $node) {
|
||||
return $node->type . '!';
|
||||
},
|
||||
|
||||
// Type System Definitions
|
||||
NodeType::SCHEMA_DEFINITION => function(SchemaDefinition $def) {
|
||||
@ -139,7 +164,9 @@ class Printer
|
||||
$this->block($def->operationTypes)
|
||||
], ' ');
|
||||
},
|
||||
NodeType::OPERATION_TYPE_DEFINITION => function(OperationTypeDefinition $def) {return $def->operation . ': ' . $def->type;},
|
||||
NodeType::OPERATION_TYPE_DEFINITION => function(OperationTypeDefinition $def) {
|
||||
return $def->operation . ': ' . $def->type;
|
||||
},
|
||||
|
||||
NodeType::SCALAR_TYPE_DEFINITION => function(ScalarTypeDefinition $def) {
|
||||
return $this->join(['scalar', $def->name, $this->join($def->directives, ' ')], ' ');
|
||||
|
@ -65,6 +65,7 @@ class Visitor
|
||||
NodeType::FLOAT => [],
|
||||
NodeType::STRING => [],
|
||||
NodeType::BOOLEAN => [],
|
||||
NodeType::NULL => [],
|
||||
NodeType::ENUM => [],
|
||||
NodeType::LST => ['values'],
|
||||
NodeType::OBJECT => ['fields'],
|
||||
|
@ -40,6 +40,11 @@ class FieldArgument
|
||||
*/
|
||||
private $resolvedType;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $defaultValueExists = false;
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
* @return array
|
||||
@ -62,17 +67,23 @@ class FieldArgument
|
||||
*/
|
||||
public function __construct(array $def)
|
||||
{
|
||||
$def += [
|
||||
'type' => null,
|
||||
'name' => null,
|
||||
'defaultValue' => null,
|
||||
'description' => null
|
||||
];
|
||||
|
||||
$this->type = $def['type'];
|
||||
$this->name = $def['name'];
|
||||
$this->description = $def['description'];
|
||||
$this->defaultValue = $def['defaultValue'];
|
||||
foreach ($def as $key => $value) {
|
||||
switch ($key) {
|
||||
case 'type':
|
||||
$this->type = $value;
|
||||
break;
|
||||
case 'name':
|
||||
$this->name = $value;
|
||||
break;
|
||||
case 'defaultValue':
|
||||
$this->defaultValue = $value;
|
||||
$this->defaultValueExists = true;
|
||||
break;
|
||||
case 'description':
|
||||
$this->description = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->config = $def;
|
||||
}
|
||||
|
||||
@ -87,4 +98,12 @@ class FieldArgument
|
||||
}
|
||||
return $this->resolvedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function defaultValueExists()
|
||||
{
|
||||
return $this->defaultValueExists;
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,13 @@ class InputObjectField
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Helps to differentiate when `defaultValue` is `null` and when it was not even set initially
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $defaultValueExists = false;
|
||||
|
||||
/**
|
||||
* InputObjectField constructor.
|
||||
* @param array $opts
|
||||
@ -34,9 +41,18 @@ class InputObjectField
|
||||
public function __construct(array $opts)
|
||||
{
|
||||
foreach ($opts as $k => $v) {
|
||||
switch ($k) {
|
||||
case 'defaultValue':
|
||||
$this->defaultValue = $v;
|
||||
$this->defaultValueExists = true;
|
||||
break;
|
||||
case 'defaultValueExists':
|
||||
break;
|
||||
default:
|
||||
$this->{$k} = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
@ -45,4 +61,12 @@ class InputObjectField
|
||||
{
|
||||
return Type::resolve($this->type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function defaultValueExists()
|
||||
{
|
||||
return $this->defaultValueExists;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use GraphQL\Language\AST\FloatValue;
|
||||
use GraphQL\Language\AST\IntValue;
|
||||
use GraphQL\Language\AST\ListValue;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\NullValue;
|
||||
use GraphQL\Language\AST\ObjectField;
|
||||
use GraphQL\Language\AST\ObjectValue;
|
||||
use GraphQL\Language\AST\StringValue;
|
||||
@ -43,21 +44,24 @@ class AST
|
||||
* | Int | Int |
|
||||
* | Float | Int / Float |
|
||||
* | Mixed | Enum Value |
|
||||
* | null | NullValue |
|
||||
*
|
||||
* @param $value
|
||||
* @param InputType $type
|
||||
* @return ObjectValue|ListValue|BooleanValue|IntValue|FloatValue|EnumValue|StringValue
|
||||
* @return ObjectValue|ListValue|BooleanValue|IntValue|FloatValue|EnumValue|StringValue|NullValue
|
||||
*/
|
||||
static function astFromValue($value, InputType $type)
|
||||
{
|
||||
if ($type instanceof NonNull) {
|
||||
// Note: we're not checking that the result is non-null.
|
||||
// This function is not responsible for validating the input value.
|
||||
return self::astFromValue($value, $type->getWrappedType());
|
||||
$astValue = self::astFromValue($value, $type->getWrappedType());
|
||||
if ($astValue instanceof NullValue) {
|
||||
return null;
|
||||
}
|
||||
return $astValue;
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
return new NullValue([]);
|
||||
}
|
||||
|
||||
// Convert PHP array to GraphQL list. If the GraphQLType is a list, but
|
||||
@ -80,7 +84,8 @@ class AST
|
||||
// Populate the fields of the input object by creating ASTs from each value
|
||||
// in the PHP object according to the fields in the input type.
|
||||
if ($type instanceof InputObjectType) {
|
||||
$isArrayLike = is_array($value) || $value instanceof \ArrayAccess;
|
||||
$isArray = is_array($value);
|
||||
$isArrayLike = $isArray || $value instanceof \ArrayAccess;
|
||||
if ($value === null || (!$isArrayLike && !is_object($value))) {
|
||||
return null;
|
||||
}
|
||||
@ -92,15 +97,31 @@ class AST
|
||||
} else {
|
||||
$fieldValue = isset($value->{$fieldName}) ? $value->{$fieldName} : null;
|
||||
}
|
||||
$fieldValue = self::astFromValue($fieldValue, $field->getType());
|
||||
|
||||
if ($fieldValue) {
|
||||
// Have to check additionally if key exists, since we differentiate between
|
||||
// "no key" and "value is null":
|
||||
if (null !== $fieldValue) {
|
||||
$fieldExists = true;
|
||||
} else if ($isArray) {
|
||||
$fieldExists = array_key_exists($fieldName, $value);
|
||||
} else if ($isArrayLike) {
|
||||
/** @var \ArrayAccess $value */
|
||||
$fieldExists = $value->offsetExists($fieldName);
|
||||
} else {
|
||||
$fieldExists = property_exists($value, $fieldName);
|
||||
}
|
||||
|
||||
if ($fieldExists) {
|
||||
$fieldNode = self::astFromValue($fieldValue, $field->getType());
|
||||
|
||||
if ($fieldNode) {
|
||||
$fieldASTs[] = new ObjectField([
|
||||
'name' => new Name(['value' => $fieldName]),
|
||||
'value' => $fieldValue
|
||||
'value' => $fieldNode
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ObjectValue(['fields' => $fieldASTs]);
|
||||
}
|
||||
|
||||
@ -165,11 +186,12 @@ class AST
|
||||
* | String | String |
|
||||
* | Int / Float | Int / Float |
|
||||
* | Enum Value | Mixed |
|
||||
* | Null Value | null |
|
||||
*
|
||||
* @param $valueAST
|
||||
* @param InputType $type
|
||||
* @param null $variables
|
||||
* @return array|null
|
||||
* @return array|null|\stdClass
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function valueFromAST($valueAST, InputType $type, $variables = null)
|
||||
@ -182,14 +204,22 @@ class AST
|
||||
}
|
||||
|
||||
if (!$valueAST) {
|
||||
return null;
|
||||
// When there is no AST, then there is also no value.
|
||||
// Importantly, this is different from returning the GraphQL null value.
|
||||
return ;
|
||||
}
|
||||
|
||||
if ($valueAST instanceof NullValue) {
|
||||
// This is explicitly returning the value null.
|
||||
return NullValue::getNullValue();
|
||||
}
|
||||
|
||||
if ($valueAST instanceof Variable) {
|
||||
$variableName = $valueAST->name->value;
|
||||
|
||||
if (!$variables || !isset($variables[$variableName])) {
|
||||
return null;
|
||||
// No valid return value.
|
||||
return ;
|
||||
}
|
||||
// Note: we're not doing any checking that this variable is correct. We're
|
||||
// assuming that this query has been validated and the variable usage here
|
||||
@ -199,19 +229,23 @@ class AST
|
||||
|
||||
if ($type instanceof ListOfType) {
|
||||
$itemType = $type->getWrappedType();
|
||||
if ($valueAST instanceof ListValue) {
|
||||
return array_map(function($itemAST) use ($itemType, $variables) {
|
||||
return self::valueFromAST($itemAST, $itemType, $variables);
|
||||
}, $valueAST->values);
|
||||
} else {
|
||||
return [self::valueFromAST($valueAST, $itemType, $variables)];
|
||||
$items = $valueAST instanceof ListValue ? $valueAST->values : [$valueAST];
|
||||
$result = [];
|
||||
foreach ($items as $itemAST) {
|
||||
$value = self::valueFromAST($itemAST, $itemType, $variables);
|
||||
if ($value === NullValue::getNullValue()) {
|
||||
$value = null;
|
||||
}
|
||||
$result[] = $value;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($type instanceof InputObjectType) {
|
||||
$fields = $type->getFields();
|
||||
if (!$valueAST instanceof ObjectValue) {
|
||||
return null;
|
||||
// No valid return value.
|
||||
return ;
|
||||
}
|
||||
$fieldASTs = Utils::keyMap($valueAST->fields, function($field) {return $field->name->value;});
|
||||
$values = [];
|
||||
@ -219,13 +253,20 @@ class AST
|
||||
$fieldAST = isset($fieldASTs[$field->name]) ? $fieldASTs[$field->name] : null;
|
||||
$fieldValue = self::valueFromAST($fieldAST ? $fieldAST->value : null, $field->getType(), $variables);
|
||||
|
||||
if (null === $fieldValue) {
|
||||
// If field is not in AST and defaultValue was never set for this field - do not include it in result
|
||||
if (null === $fieldValue && null === $field->defaultValue && !$field->defaultValueExists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set Explicit null value or default value:
|
||||
if (NullValue::getNullValue() === $fieldValue) {
|
||||
$fieldValue = null;
|
||||
} else if (null === $fieldValue) {
|
||||
$fieldValue = $field->defaultValue;
|
||||
}
|
||||
if (null !== $fieldValue) {
|
||||
|
||||
$values[$field->name] = $fieldValue;
|
||||
}
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ use GraphQL\Language\AST\Document;
|
||||
use GraphQL\Language\AST\FragmentSpread;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeType;
|
||||
use GraphQL\Language\AST\NullValue;
|
||||
use GraphQL\Language\AST\Value;
|
||||
use GraphQL\Language\AST\Variable;
|
||||
use GraphQL\Language\Printer;
|
||||
@ -155,7 +156,7 @@ class DocumentValidator
|
||||
// A value must be provided if the type is non-null.
|
||||
if ($type instanceof NonNull) {
|
||||
$wrappedType = $type->getWrappedType();
|
||||
if (!$valueAST) {
|
||||
if (!$valueAST || $valueAST instanceof NullValue) {
|
||||
if ($wrappedType->name) {
|
||||
return [ "Expected \"{$wrappedType->name}!\", found null." ];
|
||||
}
|
||||
@ -164,7 +165,7 @@ class DocumentValidator
|
||||
return static::isValidLiteralValue($wrappedType, $valueAST);
|
||||
}
|
||||
|
||||
if (!$valueAST) {
|
||||
if (!$valueAST || $valueAST instanceof NullValue) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -836,7 +836,7 @@ class ExecutorTest extends \PHPUnit_Framework_TestCase
|
||||
$result = Executor::execute($schema, $query);
|
||||
$expected = [
|
||||
'data' => [
|
||||
'field' => '{"a":1,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"}}'
|
||||
'field' => '{"a":1,"b":null,"c":0,"d":false,"e":"0","f":"some-string","h":{"a":1,"b":"test"}}'
|
||||
]
|
||||
];
|
||||
|
||||
|
@ -49,6 +49,28 @@ class VariablesTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
|
||||
// properly parses null value to null
|
||||
$doc = '
|
||||
{
|
||||
fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null})
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithObjectInput' => '{"a":null,"b":null,"c":"C","d":null}']];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
|
||||
// properly parses null value in list
|
||||
$doc = '
|
||||
{
|
||||
fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"})
|
||||
}
|
||||
';
|
||||
$ast = Parser::parse($doc);
|
||||
$expected = ['data' => ['fieldWithObjectInput' => '{"b":["A",null,"C"],"c":"C"}']];
|
||||
|
||||
$this->assertEquals($expected, Executor::execute($this->schema(), $ast)->toArray());
|
||||
|
||||
// does not use incorrect value
|
||||
$doc = '
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ use GraphQL\Language\AST\Field;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\NodeType;
|
||||
use GraphQL\Language\AST\NullValue;
|
||||
use GraphQL\Language\AST\SelectionSet;
|
||||
use GraphQL\Language\AST\StringValue;
|
||||
use GraphQL\Language\Parser;
|
||||
@ -106,15 +107,6 @@ fragment MissingOn Type
|
||||
Parser::parse('{ ...on }');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it does not allow null as value
|
||||
*/
|
||||
public function testDoesNotAllowNullAsValue()
|
||||
{
|
||||
$this->setExpectedException('GraphQL\Error\SyntaxError', 'Syntax Error GraphQL (1:39) Unexpected Name "null"');
|
||||
Parser::parse('{ fieldWithNullableStringInput(input: null) }');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it parses multi-byte characters
|
||||
*/
|
||||
@ -393,6 +385,17 @@ fragment $fragmentName on Type {
|
||||
|
||||
// Describe: parseValue
|
||||
|
||||
/**
|
||||
* @it parses null value
|
||||
*/
|
||||
public function testParsesNullValues()
|
||||
{
|
||||
$this->assertEquals([
|
||||
'kind' => NodeType::NULL,
|
||||
'loc' => ['start' => 0, 'end' => 4]
|
||||
], $this->nodeToArray(Parser::parseValue('null')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it parses list values
|
||||
*/
|
||||
|
@ -154,7 +154,7 @@ fragment frag on Friend {
|
||||
}
|
||||
|
||||
{
|
||||
unnamed(truthy: true, falsey: false)
|
||||
unnamed(truthy: true, falsey: false, nullish: null)
|
||||
query
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,7 @@ type Foo implements Bar {
|
||||
four(argument: String = "string"): String
|
||||
five(argument: [String] = ["string", "string"]): String
|
||||
six(argument: InputType = {key: "value"}): Type
|
||||
seven(argument: Int = null): Type
|
||||
}
|
||||
|
||||
type AnnotatedObject @onObject(arg: "value") {
|
||||
|
@ -636,6 +636,12 @@ class VisitorTest extends \PHPUnit_Framework_TestCase
|
||||
[ 'enter', 'BooleanValue', 'value', 'Argument' ],
|
||||
[ 'leave', 'BooleanValue', 'value', 'Argument' ],
|
||||
[ 'leave', 'Argument', 1, null ],
|
||||
[ 'enter', 'Argument', 2, null ],
|
||||
[ 'enter', 'Name', 'name', 'Argument' ],
|
||||
[ 'leave', 'Name', 'name', 'Argument' ],
|
||||
[ 'enter', 'NullValue', 'value', 'Argument' ],
|
||||
[ 'leave', 'NullValue', 'value', 'Argument' ],
|
||||
[ 'leave', 'Argument', 2, null ],
|
||||
[ 'leave', 'Field', 0, null ],
|
||||
[ 'enter', 'Field', 1, null ],
|
||||
[ 'enter', 'Name', 'name', 'Field' ],
|
||||
|
@ -52,6 +52,6 @@ fragment frag on Friend {
|
||||
}
|
||||
|
||||
{
|
||||
unnamed(truthy: true, falsey: false),
|
||||
unnamed(truthy: true, falsey: false, nullish: null),
|
||||
query
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ type Foo implements Bar {
|
||||
four(argument: String = "string"): String
|
||||
five(argument: [String] = ["string", "string"]): String
|
||||
six(argument: InputType = {key: "value"}): Type
|
||||
seven(argument: Int = null): Type
|
||||
}
|
||||
|
||||
type AnnotatedObject @onObject(arg: "value") {
|
||||
|
@ -7,6 +7,7 @@ use GraphQL\Language\AST\FloatValue;
|
||||
use GraphQL\Language\AST\IntValue;
|
||||
use GraphQL\Language\AST\ListValue;
|
||||
use GraphQL\Language\AST\Name;
|
||||
use GraphQL\Language\AST\NullValue;
|
||||
use GraphQL\Language\AST\ObjectField;
|
||||
use GraphQL\Language\AST\ObjectValue;
|
||||
use GraphQL\Language\AST\StringValue;
|
||||
@ -26,9 +27,11 @@ class ASTFromValueTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$this->assertEquals(new BooleanValue(['value' => true]), AST::astFromValue(true, Type::boolean()));
|
||||
$this->assertEquals(new BooleanValue(['value' => false]), AST::astFromValue(false, Type::boolean()));
|
||||
$this->assertEquals(null, AST::astFromValue(null, Type::boolean()));
|
||||
$this->assertEquals(new NullValue([]), AST::astFromValue(null, Type::boolean()));
|
||||
$this->assertEquals(new BooleanValue(['value' => false]), AST::astFromValue(0, Type::boolean()));
|
||||
$this->assertEquals(new BooleanValue(['value' => true]), AST::astFromValue(1, Type::boolean()));
|
||||
$this->assertEquals(new BooleanValue(['value' => false]), AST::astFromValue(0, Type::nonNull(Type::boolean())));
|
||||
$this->assertEquals(null, AST::astFromValue(null, Type::nonNull(Type::boolean()))); // Note: null means that AST cannot
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,7 +73,8 @@ class ASTFromValueTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals(new StringValue(['value' => 'VA\\nLUE']), AST::astFromValue("VA\nLUE", Type::string()));
|
||||
$this->assertEquals(new StringValue(['value' => '123']), AST::astFromValue(123, Type::string()));
|
||||
$this->assertEquals(new StringValue(['value' => 'false']), AST::astFromValue(false, Type::string()));
|
||||
$this->assertEquals(null, AST::astFromValue(null, Type::string()));
|
||||
$this->assertEquals(new NullValue([]), AST::astFromValue(null, Type::string()));
|
||||
$this->assertEquals(null, AST::astFromValue(null, Type::nonNull(Type::string())));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,7 +87,16 @@ class ASTFromValueTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals(new StringValue(['value' => 'VA\\nLUE']), AST::astFromValue("VA\nLUE", Type::id()));
|
||||
$this->assertEquals(new IntValue(['value' => '123']), AST::astFromValue(123, Type::id()));
|
||||
$this->assertEquals(new StringValue(['value' => 'false']), AST::astFromValue(false, Type::id()));
|
||||
$this->assertEquals(null, AST::astFromValue(null, Type::id()));
|
||||
$this->assertEquals(new NullValue([]), AST::astFromValue(null, Type::id()));
|
||||
$this->assertEquals(null, AST::astFromValue(null, Type::nonNull(Type::id())));
|
||||
}
|
||||
|
||||
/**
|
||||
* @it does not converts NonNull values to NullValue
|
||||
*/
|
||||
public function testDoesNotConvertsNonNullValuestoNullValue()
|
||||
{
|
||||
$this->assertSame(null, AST::astFromValue(null, Type::nonNull(Type::boolean())));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,6 +169,44 @@ class ASTFromValueTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals($expected, AST::astFromValue((object) $data, $inputObj));
|
||||
}
|
||||
|
||||
public function testConvertsInputObjectsWithExplicitNulls()
|
||||
{
|
||||
$inputObj = new InputObjectType([
|
||||
'name' => 'MyInputObj',
|
||||
'fields' => [
|
||||
'foo' => Type::float(),
|
||||
'bar' => $this->myEnum()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(new ObjectValue([
|
||||
'fields' => [
|
||||
$this->objectField('foo', new NullValue([]))
|
||||
]
|
||||
]), AST::astFromValue(['foo' => null], $inputObj));
|
||||
/*
|
||||
const inputObj = new GraphQLInputObjectType({
|
||||
name: 'MyInputObj',
|
||||
fields: {
|
||||
foo: { type: GraphQLFloat },
|
||||
bar: { type: myEnum },
|
||||
}
|
||||
});
|
||||
|
||||
expect(astFromValue(
|
||||
{ foo: null },
|
||||
inputObj
|
||||
)).to.deep.equal(
|
||||
{ kind: 'ObjectValue',
|
||||
fields: [
|
||||
{ kind: 'ObjectField',
|
||||
name: { kind: 'Name', value: 'foo' },
|
||||
value: { kind: 'NullValue' } } ] }
|
||||
);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
private $complexValue;
|
||||
|
||||
private function complexValue()
|
||||
|
@ -132,6 +132,28 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it null into nullable type
|
||||
*/
|
||||
public function testNullIntoNullableType()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
{
|
||||
complicatedArgs {
|
||||
intArgField(intArg: null)
|
||||
}
|
||||
}
|
||||
');
|
||||
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
{
|
||||
dog(a: null, b: null, c:{ requiredField: true, intField: null }) {
|
||||
name
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
// Invalid String values
|
||||
|
||||
/**
|
||||
@ -574,6 +596,20 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Null value
|
||||
*/
|
||||
public function testNullValue()
|
||||
{
|
||||
$this->expectPassesRule(new ArgumentsOfCorrectType(), '
|
||||
{
|
||||
complicatedArgs {
|
||||
stringListArgField(stringListArg: null)
|
||||
}
|
||||
}
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Single value into List
|
||||
*/
|
||||
@ -801,6 +837,24 @@ class ArgumentsOfCorrectTypeTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @it Null value
|
||||
*/
|
||||
public function testNullValue2()
|
||||
{
|
||||
$this->expectFailsRule(new ArgumentsOfCorrectType(), '
|
||||
{
|
||||
complicatedArgs {
|
||||
multipleReqs(req1: null)
|
||||
}
|
||||
}
|
||||
', [
|
||||
$this->badValue('req1', 'Int!', 'null', 4, 32, [
|
||||
'Expected "Int!", found null.'
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// Valid input object value
|
||||
|
||||
|
@ -50,6 +50,52 @@ class DefaultValuesOfCorrectTypeTest extends TestCase
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user