diff --git a/src/Executor/Values.php b/src/Executor/Values.php index ef6a8cf..c8353c4 100644 --- a/src/Executor/Values.php +++ b/src/Executor/Values.php @@ -115,7 +115,6 @@ class Values } $coercedValues = []; - $undefined = Utils::undefined(); /** @var ArgumentNode[] $argNodeMap */ $argNodeMap = $argNodes ? Utils::keyMap($argNodes, function (ArgumentNode $arg) { @@ -158,11 +157,12 @@ class Values } else { $valueNode = $argumentNode->value; $coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues); - if ($coercedValue === $undefined) { - $errors = DocumentValidator::isValidLiteralValue($argType, $valueNode); - $message = !empty($errors) ? ("\n" . implode("\n", $errors)) : ''; + if (Utils::isInvalid($coercedValue)) { + // Note: ValuesOfCorrectType validation should catch this before + // execution. This is a runtime check to ensure execution does not + // continue with an invalid argument value. throw new Error( - 'Argument "' . $name . '" got invalid value ' . Printer::doPrint($valueNode) . '.' . $message, + 'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.', [ $argumentNode->value ] ); } diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 843a433..a211c5a 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -4,7 +4,6 @@ namespace GraphQL\Utils; use GraphQL\Error\InvariantViolation; use GraphQL\Error\Warning; use GraphQL\Language\AST\FieldNode; -use GraphQL\Language\AST\ListType; use GraphQL\Language\AST\ListTypeNode; use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\Node; @@ -20,7 +19,6 @@ use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InputType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ListOfType; -use GraphQL\Type\Definition\NonNull; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; @@ -217,14 +215,26 @@ class TypeInfo /** * TypeInfo constructor. * @param Schema $schema + * @param Type|null $initialType */ - public function __construct(Schema $schema) + public function __construct(Schema $schema, $initialType = null) { $this->schema = $schema; $this->typeStack = []; $this->parentTypeStack = []; $this->inputTypeStack = []; $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() { @@ -260,6 +270,17 @@ class TypeInfo return null; } + /** + * @return InputType|null + */ + public function getParentInputType() + { + $inputTypeStackLength = count($this->inputTypeStack); + if ($inputTypeStackLength > 1) { + return $this->inputTypeStack[$inputTypeStackLength - 2]; + } + } + /** * @return FieldDefinition */ @@ -369,10 +390,9 @@ class TypeInfo case NodeKind::LST: $listType = Type::getNullableType($this->getInputType()); - $itemType = null; - if ($itemType instanceof ListType) { - $itemType = $listType->getWrappedType(); - } + $itemType = $listType instanceof ListOfType + ? $listType->getWrappedType() + : $listType; $this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null; break; diff --git a/src/Validator/DocumentValidator.php b/src/Validator/DocumentValidator.php index 1a454f7..3efd992 100644 --- a/src/Validator/DocumentValidator.php +++ b/src/Validator/DocumentValidator.php @@ -2,26 +2,13 @@ namespace GraphQL\Validator; use GraphQL\Error\Error; -use GraphQL\Language\AST\EnumValueNode; -use GraphQL\Language\AST\ListValueNode; 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\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\ScalarType; -use GraphQL\Utils\Utils; use GraphQL\Utils\TypeInfo; use GraphQL\Validator\Rules\AbstractValidationRule; -use GraphQL\Validator\Rules\ArgumentsOfCorrectType; -use GraphQL\Validator\Rules\DefaultValuesOfCorrectType; +use GraphQL\Validator\Rules\ValuesOfCorrectType; use GraphQL\Validator\Rules\DisableIntrospection; use GraphQL\Validator\Rules\ExecutableDefinitions; use GraphQL\Validator\Rules\FieldsOnCorrectType; @@ -48,6 +35,7 @@ use GraphQL\Validator\Rules\UniqueInputFieldNames; use GraphQL\Validator\Rules\UniqueOperationNames; use GraphQL\Validator\Rules\UniqueVariableNames; use GraphQL\Validator\Rules\VariablesAreInputTypes; +use GraphQL\Validator\Rules\VariablesDefaultValueAllowed; use GraphQL\Validator\Rules\VariablesInAllowedPosition; /** @@ -144,9 +132,9 @@ class DocumentValidator UniqueDirectivesPerLocation::class => new UniqueDirectivesPerLocation(), KnownArgumentNames::class => new KnownArgumentNames(), UniqueArgumentNames::class => new UniqueArgumentNames(), - ArgumentsOfCorrectType::class => new ArgumentsOfCorrectType(), + ValuesOfCorrectType::class => new ValuesOfCorrectType(), ProvidedNonNullArguments::class => new ProvidedNonNullArguments(), - DefaultValuesOfCorrectType::class => new DefaultValuesOfCorrectType(), + VariablesDefaultValueAllowed::class => new VariablesDefaultValueAllowed(), VariablesInAllowedPosition::class => new VariablesInAllowedPosition(), OverlappingFieldsCanBeMerged::class => new OverlappingFieldsCanBeMerged(), UniqueInputFieldNames::class => new UniqueInputFieldNames(), @@ -226,121 +214,23 @@ class DocumentValidator } /** - * Utility for validators which determines if a value literal AST is valid given - * an input type. + * Utility which determines if a value literal node is valid for an input type. * - * Note that this only validates literal values, variables are assumed to - * provide values of the correct type. + * Deprecated. Rely on validation for documents containing literal values. * - * @return array + * @deprecated + * @return Error[] */ public static function isValidLiteralValue(Type $type, $valueNode) { - // A value must be provided if the type is non-null. - if ($type instanceof NonNull) { - if (!$valueNode || $valueNode instanceof NullValueNode) { - return [ 'Expected "' . Utils::printSafe($type) . '", found null.' ]; - } - return static::isValidLiteralValue($type->getWrappedType(), $valueNode); - } - - 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) . '.'); + $emptySchema = new Schema([]); + $emptyDoc = new DocumentNode(['definitions' => []]); + $typeInfo = new TypeInfo($emptySchema, $type); + $context = new ValidationContext($emptySchema, $emptyDoc, $typeInfo); + $validator = new ValuesOfCorrectType(); + $visitor = $validator->getVisitor($context); + Visitor::visit($valueNode, Visitor::visitWithTypeInfo($typeInfo, $visitor)); + return $context->getErrors(); } /** diff --git a/src/Validator/Rules/ArgumentsOfCorrectType.php b/src/Validator/Rules/ArgumentsOfCorrectType.php deleted file mode 100644 index 3e37322..0000000 --- a/src/Validator/Rules/ArgumentsOfCorrectType.php +++ /dev/null @@ -1,39 +0,0 @@ - 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(); - } - ]; - } -} diff --git a/src/Validator/Rules/DefaultValuesOfCorrectType.php b/src/Validator/Rules/DefaultValuesOfCorrectType.php deleted file mode 100644 index 792acd7..0000000 --- a/src/Validator/Rules/DefaultValuesOfCorrectType.php +++ /dev/null @@ -1,59 +0,0 @@ - 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();} - ]; - } -} diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php new file mode 100644 index 0000000..a70de1f --- /dev/null +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -0,0 +1,226 @@ + 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) . '?' : ''; + } +} diff --git a/src/Validator/Rules/VariablesDefaultValueAllowed.php b/src/Validator/Rules/VariablesDefaultValueAllowed.php new file mode 100644 index 0000000..fcbbef4 --- /dev/null +++ b/src/Validator/Rules/VariablesDefaultValueAllowed.php @@ -0,0 +1,60 @@ + 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(); + }, + ]; + } +} diff --git a/src/Validator/ValidationContext.php b/src/Validator/ValidationContext.php index 51ea8d1..4d82ce4 100644 --- a/src/Validator/ValidationContext.php +++ b/src/Validator/ValidationContext.php @@ -12,11 +12,9 @@ use GraphQL\Error\Error; use GraphQL\Type\Schema; use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\FragmentDefinitionNode; -use GraphQL\Language\AST\Node; use GraphQL\Type\Definition\CompositeType; use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InputType; -use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\Type; use GraphQL\Utils\TypeInfo; @@ -275,6 +273,14 @@ class ValidationContext return $this->typeInfo->getInputType(); } + /** + * @return InputType + */ + function getParentInputType() + { + return $this->typeInfo->getParentInputType(); + } + /** * @return FieldDefinition */ diff --git a/tests/Executor/VariablesTest.php b/tests/Executor/VariablesTest.php index 0180bbd..ccb16bb 100644 --- a/tests/Executor/VariablesTest.php +++ b/tests/Executor/VariablesTest.php @@ -4,7 +4,6 @@ namespace GraphQL\Tests\Executor; require_once __DIR__ . '/TestClasses.php'; use GraphQL\Error\Error; -use GraphQL\Error\InvariantViolation; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; use GraphQL\Type\Schema; @@ -82,9 +81,9 @@ class VariablesTest extends \PHPUnit_Framework_TestCase $expected = [ 'data' => ['fieldWithObjectInput' => null], 'errors' => [[ - 'message' => 'Argument "input" got invalid value ["foo", "bar", "baz"].' . "\n" . - 'Expected "TestInputObject", found not an object.', - 'path' => ['fieldWithObjectInput'] + 'message' => 'Argument "input" has invalid value ["foo", "bar", "baz"].', + 'path' => ['fieldWithObjectInput'], + 'locations' => [['line' => 3, 'column' => 39]] ]] ]; $this->assertArraySubset($expected, $result); @@ -877,8 +876,7 @@ class VariablesTest extends \PHPUnit_Framework_TestCase 'data' => ['fieldWithDefaultArgumentValue' => null], 'errors' => [[ 'message' => - 'Argument "input" got invalid value WRONG_TYPE.' . "\n" . - 'Expected type "String", found WRONG_TYPE.', + 'Argument "input" has invalid value WRONG_TYPE.', 'locations' => [ [ 'line' => 2, 'column' => 50 ] ], 'path' => [ 'fieldWithDefaultArgumentValue' ], 'category' => 'graphql', diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index 0761cb1..9c6910d 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -220,7 +220,22 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase '{ colorEnum(fromEnum: "GREEN") }', 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)] ] ); @@ -236,7 +251,8 @@ class EnumTypeTest extends \PHPUnit_Framework_TestCase null, [ '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( '{ colorEnum(fromEnum: 1) }', 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( '{ colorEnum(fromInt: GREEN) }', null, - "Argument \"fromInt\" has invalid value GREEN.\nExpected type \"Int\", found GREEN." + "Expected type Int, found GREEN." ); } diff --git a/tests/Utils/IsValidLiteralValueTest.php b/tests/Utils/IsValidLiteralValueTest.php new file mode 100644 index 0000000..33b0592 --- /dev/null +++ b/tests/Utils/IsValidLiteralValueTest.php @@ -0,0 +1,37 @@ +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()); + } +} diff --git a/tests/Validator/DefaultValuesOfCorrectTypeTest.php b/tests/Validator/DefaultValuesOfCorrectTypeTest.php deleted file mode 100644 index a0ac412..0000000 --- a/tests/Validator/DefaultValuesOfCorrectTypeTest.php +++ /dev/null @@ -1,188 +0,0 @@ -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) ] - ); - } -} diff --git a/tests/Validator/TestCase.php b/tests/Validator/TestCase.php index 770e650..164effa 100644 --- a/tests/Validator/TestCase.php +++ b/tests/Validator/TestCase.php @@ -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([ 'name' => 'Invalid', '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([ 'name' => 'QueryRoot', 'fields' => [ @@ -295,16 +295,16 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase 'dogOrHuman' => ['type' => $DogOrHuman], 'humanOrAlien' => ['type' => $HumanOrAlien], 'complicatedArgs' => ['type' => $ComplicatedArgs], - 'anyArg' => [ - 'args' => ['arg' => ['type' => $anyScalar]], - 'type' => Type::string(), - ], 'invalidArg' => [ 'args' => [ 'arg' => ['type' => $invalidScalar] ], 'type' => Type::string(), - ] + ], + 'anyArg' => [ + 'args' => ['arg' => ['type' => $anyScalar]], + 'type' => Type::string(), + ], ] ]); diff --git a/tests/Validator/ValidationTest.php b/tests/Validator/ValidationTest.php index 6cab583..51c7dbf 100644 --- a/tests/Validator/ValidationTest.php +++ b/tests/Validator/ValidationTest.php @@ -1,10 +1,6 @@ "Argument \"arg\" has invalid value \"bad value\". -Expected type \"Invalid\", found \"bad value\"; Invalid scalar is always invalid: bad value", + 'message' => "Expected type Invalid, found \"bad value\"; Invalid scalar is always invalid: bad value", 'locations' => [ ['line' => 3, 'column' => 25] ] ]; diff --git a/tests/Validator/ArgumentsOfCorrectTypeTest.php b/tests/Validator/ValuesOfCorrectTypeTest.php similarity index 58% rename from tests/Validator/ArgumentsOfCorrectTypeTest.php rename to tests/Validator/ValuesOfCorrectTypeTest.php index 1f1c9df..dd62ebd 100644 --- a/tests/Validator/ArgumentsOfCorrectTypeTest.php +++ b/tests/Validator/ValuesOfCorrectTypeTest.php @@ -3,21 +3,44 @@ namespace GraphQL\Tests\Validator; use GraphQL\Error\FormattedError; use GraphQL\Language\SourceLocation; -use GraphQL\Validator\Rules\ArgumentsOfCorrectType; +use GraphQL\Validator\Rules\ValuesOfCorrectType; -class ArgumentsOfCorrectTypeTest extends TestCase +class ValuesOfCorrectTypeTest extends TestCase { - function badValue($argName, $typeName, $value, $line, $column, $errors = null) + private function badValue($typeName, $value, $line, $column, $message = null) { - $realErrors = !$errors ? ["Expected type \"$typeName\", found $value."] : $errors; - return FormattedError::create( - ArgumentsOfCorrectType::badValueMessage($argName, $typeName, $value, $realErrors), + ValuesOfCorrectType::badValueMessage( + $typeName, + $value, + $message + ), [new SourceLocation($line, $column)] ); } - // Validate: Argument values of correct type + private function requiredField($typeName, $fieldName, $fieldTypeName, $line, $column) { + return FormattedError::create( + ValuesOfCorrectType::requiredFieldMessage( + $typeName, + $fieldName, + $fieldTypeName + ), + [new SourceLocation($line, $column)] + ); + } + + private function unknownField($typeName, $fieldName, $line, $column) { + return FormattedError::create( + ValuesOfCorrectType::unknownFieldMessage( + $typeName, + $fieldName + ), + [new SourceLocation($line, $column)] + ); + } + + // Validate: Values of correct type // Valid values /** @@ -25,7 +48,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testGoodIntValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { intArgField(intArg: 2) @@ -39,7 +62,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testGoodNegativeIntValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { intArgField(intArg: -2) @@ -53,7 +76,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testGoodBooleanValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { booleanArgField(booleanArg: true) @@ -67,7 +90,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testGoodStringValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { stringArgField(stringArg: "foo") @@ -81,7 +104,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testGoodFloatValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { floatArgField(floatArg: 1.1) @@ -92,7 +115,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase public function testGoodNegativeFloatValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { floatArgField(floatArg: -1.1) @@ -106,7 +129,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testIntIntoFloat() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { floatArgField(floatArg: 1) @@ -120,7 +143,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testIntIntoID() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { idArgField(idArg: 1) @@ -134,7 +157,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testStringIntoID() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { idArgField(idArg: "someIdString") @@ -148,7 +171,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testGoodEnumValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { dog { doesKnowCommand(dogCommand: SIT) @@ -162,7 +185,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testEnumWithNullValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { enumArgField(enumArg: NO_FUR) @@ -176,7 +199,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testNullIntoNullableType() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { intArgField(intArg: null) @@ -184,7 +207,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase } '); - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { dog(a: null, b: null, c:{ requiredField: true, intField: null }) { name @@ -200,14 +223,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testIntIntoString() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { stringArgField(stringArg: 1) } } ', [ - $this->badValue('stringArg', 'String', '1', 4, 39) + $this->badValue('String', '1', 4, 39) ]); } @@ -216,14 +239,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testFloatIntoString() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { stringArgField(stringArg: 1.0) } } ', [ - $this->badValue('stringArg', 'String', '1.0', 4, 39) + $this->badValue('String', '1.0', 4, 39) ]); } @@ -232,14 +255,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testBooleanIntoString() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { stringArgField(stringArg: true) } } ', [ - $this->badValue('stringArg', 'String', 'true', 4, 39) + $this->badValue('String', 'true', 4, 39) ]); } @@ -248,14 +271,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testUnquotedStringIntoString() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { stringArgField(stringArg: BAR) } } ', [ - $this->badValue('stringArg', 'String', 'BAR', 4, 39) + $this->badValue('String', 'BAR', 4, 39) ]); } @@ -266,14 +289,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testStringIntoInt() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { intArgField(intArg: "3") } } ', [ - $this->badValue('intArg', 'Int', '"3"', 4, 33) + $this->badValue('Int', '"3"', 4, 33) ]); } @@ -282,14 +305,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testBigIntIntoInt() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { intArgField(intArg: 829384293849283498239482938) } } ', [ - $this->badValue('intArg', 'Int', '829384293849283498239482938', 4, 33) + $this->badValue('Int', '829384293849283498239482938', 4, 33) ]); } @@ -298,14 +321,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testUnquotedStringIntoInt() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { intArgField(intArg: FOO) } } ', [ - $this->badValue('intArg', 'Int', 'FOO', 4, 33) + $this->badValue('Int', 'FOO', 4, 33) ]); } @@ -314,14 +337,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testSimpleFloatIntoInt() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { intArgField(intArg: 3.0) } } ', [ - $this->badValue('intArg', 'Int', '3.0', 4, 33) + $this->badValue('Int', '3.0', 4, 33) ]); } @@ -330,14 +353,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testFloatIntoInt() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { intArgField(intArg: 3.333) } } ', [ - $this->badValue('intArg', 'Int', '3.333', 4, 33) + $this->badValue('Int', '3.333', 4, 33) ]); } @@ -348,14 +371,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testStringIntoFloat() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { floatArgField(floatArg: "3.333") } } ', [ - $this->badValue('floatArg', 'Float', '"3.333"', 4, 37) + $this->badValue('Float', '"3.333"', 4, 37) ]); } @@ -364,14 +387,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testBooleanIntoFloat() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { floatArgField(floatArg: true) } } ', [ - $this->badValue('floatArg', 'Float', 'true', 4, 37) + $this->badValue('Float', 'true', 4, 37) ]); } @@ -380,14 +403,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testUnquotedIntoFloat() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { floatArgField(floatArg: FOO) } } ', [ - $this->badValue('floatArg', 'Float', 'FOO', 4, 37) + $this->badValue('Float', 'FOO', 4, 37) ]); } @@ -398,14 +421,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testIntIntoBoolean() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { booleanArgField(booleanArg: 2) } } ', [ - $this->badValue('booleanArg', 'Boolean', '2', 4, 41) + $this->badValue('Boolean', '2', 4, 41) ]); } @@ -414,14 +437,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testFloatIntoBoolean() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { booleanArgField(booleanArg: 1.0) } } ', [ - $this->badValue('booleanArg', 'Boolean', '1.0', 4, 41) + $this->badValue('Boolean', '1.0', 4, 41) ]); } @@ -430,14 +453,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testStringIntoBoolean() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { booleanArgField(booleanArg: "true") } } ', [ - $this->badValue('booleanArg', 'Boolean', '"true"', 4, 41) + $this->badValue('Boolean', '"true"', 4, 41) ]); } @@ -446,14 +469,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testUnquotedIntoBoolean() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { booleanArgField(booleanArg: TRUE) } } ', [ - $this->badValue('booleanArg', 'Boolean', 'TRUE', 4, 41) + $this->badValue('Boolean', 'TRUE', 4, 41) ]); } @@ -464,14 +487,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testFloatIntoID() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { idArgField(idArg: 1.0) } } ', [ - $this->badValue('idArg', 'ID', '1.0', 4, 31) + $this->badValue('ID', '1.0', 4, 31) ]); } @@ -480,14 +503,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testBooleanIntoID() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { idArgField(idArg: true) } } ', [ - $this->badValue('idArg', 'ID', 'true', 4, 31) + $this->badValue('ID', 'true', 4, 31) ]); } @@ -496,14 +519,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testUnquotedIntoID() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { idArgField(idArg: SOMETHING) } } ', [ - $this->badValue('idArg', 'ID', 'SOMETHING', 4, 31) + $this->badValue('ID', 'SOMETHING', 4, 31) ]); } @@ -514,14 +537,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testIntIntoEnum() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { dog { doesKnowCommand(dogCommand: 2) } } ', [ - $this->badValue('dogCommand', 'DogCommand', '2', 4, 41) + $this->badValue('DogCommand', '2', 4, 41) ]); } @@ -530,14 +553,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testFloatIntoEnum() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { dog { doesKnowCommand(dogCommand: 1.0) } } ', [ - $this->badValue('dogCommand', 'DogCommand', '1.0', 4, 41) + $this->badValue('DogCommand', '1.0', 4, 41) ]); } @@ -546,14 +569,20 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testStringIntoEnum() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { dog { doesKnowCommand(dogCommand: "SIT") } } ', [ - $this->badValue('dogCommand', 'DogCommand', '"SIT"', 4, 41) + $this->badValue( + 'DogCommand', + '"SIT"', + 4, + 41, + 'Did you mean the enum value: SIT?' + ) ]); } @@ -562,14 +591,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testBooleanIntoEnum() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { dog { doesKnowCommand(dogCommand: true) } } ', [ - $this->badValue('dogCommand', 'DogCommand', 'true', 4, 41) + $this->badValue('DogCommand', 'true', 4, 41) ]); } @@ -578,14 +607,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testUnknownEnumValueIntoEnum() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { dog { doesKnowCommand(dogCommand: JUGGLE) } } ', [ - $this->badValue('dogCommand', 'DogCommand', 'JUGGLE', 4, 41) + $this->badValue('DogCommand', 'JUGGLE', 4, 41) ]); } @@ -594,14 +623,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testDifferentCaseEnumValueIntoEnum() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { dog { doesKnowCommand(dogCommand: sit) } } ', [ - $this->badValue('dogCommand', 'DogCommand', 'sit', 4, 41) + $this->badValue('DogCommand', 'sit', 4, 41) ]); } @@ -612,7 +641,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testGoodListValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { stringListArgField(stringListArg: ["one", null, "two"]) @@ -626,7 +655,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testEmptyListValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { stringListArgField(stringListArg: []) @@ -640,7 +669,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testNullValue() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { stringListArgField(stringListArg: null) @@ -654,7 +683,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testSingleValueIntoList() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { stringListArgField(stringListArg: "one") @@ -670,16 +699,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testIncorrectItemtype() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { stringListArgField(stringListArg: ["one", 2]) } } ', [ - $this->badValue('stringListArg', '[String]', '["one", 2]', 4, 47, [ - 'In element #1: Expected type "String", found 2.' - ]), + $this->badValue('String', '2', 4, 55), ]); } @@ -688,14 +715,14 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testSingleValueOfIncorrectType() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { stringListArgField(stringListArg: 1) } } ', [ - $this->badValue('stringListArg', 'String', '1', 4, 47), + $this->badValue('[String]', '1', 4, 47), ]); } @@ -706,7 +733,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testArgOnOptionalArg() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { dog { isHousetrained(atOtherHomes: true) @@ -720,7 +747,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testNoArgOnOptionalArg() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { dog { isHousetrained @@ -734,7 +761,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testMultipleArgs() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleReqs(req1: 1, req2: 2) @@ -748,7 +775,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testMultipleArgsReverseOrder() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleReqs(req2: 2, req1: 1) @@ -762,7 +789,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testNoArgsOnMultipleOptional() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleOpts @@ -776,7 +803,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testOneArgOnMultipleOptional() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleOpts(opt1: 1) @@ -790,7 +817,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testSecondArgOnMultipleOptional() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleOpts(opt2: 1) @@ -804,7 +831,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testMultipleReqsOnMixedList() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4) @@ -818,7 +845,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testMultipleReqsAndOneOptOnMixedList() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5) @@ -832,7 +859,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testAllReqsAndOptsOnMixedList() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6) @@ -848,31 +875,31 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testIncorrectValueType() { - $this->expectFailsRule(new ArgumentsOfCorrectType, ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleReqs(req2: "two", req1: "one") } } ', [ - $this->badValue('req2', 'Int', '"two"', 4, 32), - $this->badValue('req1', 'Int', '"one"', 4, 45), + $this->badValue('Int!', '"two"', 4, 32), + $this->badValue('Int!', '"one"', 4, 45), ]); } /** - * @it Incorrect value and missing argument + * @it Incorrect value and missing argument (ProvidedNonNullArguments) */ - public function testIncorrectValueAndMissingArgument() + public function testIncorrectValueAndMissingArgumentProvidedNonNullArguments() { - $this->expectFailsRule(new ArgumentsOfCorrectType, ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleReqs(req1: "one") } } ', [ - $this->badValue('req1', 'Int', '"one"', 4, 32), + $this->badValue('Int!', '"one"', 4, 32), ]); } @@ -881,28 +908,26 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testNullValue2() { - $this->expectFailsRule(new ArgumentsOfCorrectType(), ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { multipleReqs(req1: null) } } ', [ - $this->badValue('req1', 'Int!', 'null', 4, 32, [ - 'Expected "Int!", found null.' - ]), + $this->badValue('Int!', 'null', 4, 32), ]); } - // Valid input object value + // DESCRIBE: Valid input object value /** * @it Optional arg, despite required field in type */ public function testOptionalArgDespiteRequiredFieldInType() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { complexArgField @@ -916,7 +941,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testPartialObjectOnlyRequired() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { complexArgField(complexArg: { requiredField: true }) @@ -930,7 +955,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testPartialObjectRequiredFieldCanBeFalsey() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { complexArgField(complexArg: { requiredField: false }) @@ -944,7 +969,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testPartialObjectIncludingRequired() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { complexArgField(complexArg: { requiredField: true, intField: 4 }) @@ -958,7 +983,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testFullObject() { - $this->expectPassesRule(new ArgumentsOfCorrectType, ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { complexArgField(complexArg: { @@ -978,7 +1003,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testFullObjectWithFieldsInDifferentOrder() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { complicatedArgs { complexArgField(complexArg: { @@ -993,23 +1018,21 @@ class ArgumentsOfCorrectTypeTest extends TestCase '); } - // Invalid input object value + // DESCRIBE: Invalid input object value /** * @it Partial object, missing required */ public function testPartialObjectMissingRequired() { - $this->expectFailsRule(new ArgumentsOfCorrectType, ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { complexArgField(complexArg: { intField: 4 }) } } ', [ - $this->badValue('complexArg', 'ComplexInput', '{intField: 4}', 4, 41, [ - 'In field "requiredField": Expected "Boolean!", found null.' - ]), + $this->requiredField('ComplexInput', 'requiredField', 'Boolean!', 4, 41), ]); } @@ -1018,7 +1041,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testPartialObjectInvalidFieldType() { - $this->expectFailsRule(new ArgumentsOfCorrectType, ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { complexArgField(complexArg: { @@ -1028,14 +1051,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase } } ', [ - $this->badValue( - 'complexArg', - 'ComplexInput', - '{stringListField: ["one", 2], requiredField: true}', - 4, - 41, - [ 'In field "stringListField": In element #1: Expected type "String", found 2.' ] - ), + $this->badValue('String', '2', 5, 40), ]); } @@ -1044,7 +1060,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testPartialObjectUnknownFieldArg() { - $this->expectFailsRule(new ArgumentsOfCorrectType, ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { complicatedArgs { complexArgField(complexArg: { @@ -1054,25 +1070,60 @@ class ArgumentsOfCorrectTypeTest extends TestCase } } ', [ - $this->badValue( - 'complexArg', - 'ComplexInput', - '{requiredField: true, unknownField: "value"}', - 4, - 41, - [ 'In field "unknownField": Unknown field.' ] - ), + $this->unknownField('ComplexInput', 'unknownField', 6, 15), ]); } - // Directive arguments + + + /** + * @it reports original error for custom scalar which throws + */ + public function testReportsOriginalErrorForCustomScalarWhichThrows() + { + $errors = $this->expectFailsRule(new ValuesOfCorrectType, ' + { + invalidArg(arg: 123) + } + ', [ + $this->badValue( + 'Invalid', + '123', + 3, + 27, + 'Invalid scalar is always invalid: 123' + ), + ]); + + $this->assertEquals( + 'Invalid scalar is always invalid: 123', + $errors[0]->getPrevious()->getMessage() + ); + } + + /** + * @it allows custom scalar to accept complex literals + */ + public function testAllowsCustomScalarToAcceptComplexLiterals() + { + $this->expectPassesRule(new ValuesOfCorrectType, ' + { + test1: anyArg(arg: 123) + test2: anyArg(arg: "abc") + test3: anyArg(arg: [123, "abc"]) + test4: anyArg(arg: {deep: [123, "abc"]}) + } + '); + } + + // DESCRIBE: Directive arguments /** * @it with directives of valid types */ public function testWithDirectivesOfValidTypes() { - $this->expectPassesRule(new ArgumentsOfCorrectType(), ' + $this->expectPassesRule(new ValuesOfCorrectType, ' { dog @include(if: true) { name @@ -1081,7 +1132,7 @@ class ArgumentsOfCorrectTypeTest extends TestCase name } } - '); + '); } /** @@ -1089,15 +1140,134 @@ class ArgumentsOfCorrectTypeTest extends TestCase */ public function testWithDirectiveWithIncorrectTypes() { - $this->expectFailsRule(new ArgumentsOfCorrectType, ' + $this->expectFailsRule(new ValuesOfCorrectType, ' { dog @include(if: "yes") { name @skip(if: ENUM) } } - ', [ - $this->badValue('if', 'Boolean', '"yes"', 3, 28), - $this->badValue('if', 'Boolean', 'ENUM', 4, 28), + ', [ + $this->badValue('Boolean!', '"yes"', 3, 28), + $this->badValue('Boolean!', 'ENUM', 4, 28), + ]); + } + + // DESCRIBE: Variable default values + + /** + * @it variables with valid default values + */ + public function testVariablesWithValidDefaultValues() + { + $this->expectPassesRule(new ValuesOfCorrectType, ' + 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 ValuesOfCorrectType, ' + 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 ValuesOfCorrectType, ' + query WithDefaultValues( + $a: Int! = null, + $b: String! = null, + $c: ComplexInput = { requiredField: null, intField: null } + ) { + dog { name } + } + ', [ + $this->badValue('Int!', 'null', 3, 22), + $this->badValue('String!', 'null', 4, 25), + $this->badValue('Boolean!', 'null', 5, 47), + ]); + } + + /** + * @it variables with invalid default values + */ + public function testVariablesWithInvalidDefaultValues() + { + $this->expectFailsRule(new ValuesOfCorrectType, ' + query InvalidDefaultValues( + $a: Int = "one", + $b: String = 4, + $c: ComplexInput = "notverycomplex" + ) { + dog { name } + } + ', [ + $this->badValue('Int', '"one"', 3, 21), + $this->badValue('String', '4', 4, 24), + $this->badValue('ComplexInput', '"notverycomplex"', 5, 30), + ]); + } + + /** + * @it variables with complex invalid default values + */ + public function testVariablesWithComplexInvalidDefaultValues() + { + $this->expectFailsRule(new ValuesOfCorrectType, ' + query WithDefaultValues( + $a: ComplexInput = { requiredField: 123, intField: "abc" } + ) { + dog { name } + } + ', [ + $this->badValue('Boolean!', '123', 3, 47), + $this->badValue('Int', '"abc"', 3, 62), + ]); + } + + /** + * @it complex variables missing required field + */ + public function testComplexVariablesMissingRequiredField() + { + $this->expectFailsRule(new ValuesOfCorrectType, ' + query MissingRequiredField($a: ComplexInput = {intField: 3}) { + dog { name } + } + ', [ + $this->requiredField('ComplexInput', 'requiredField', 'Boolean!', 2, 55), + ]); + } + + /** + * @it list variables with invalid item + */ + public function testListVariablesWithInvalidItem() + { + $this->expectFailsRule(new ValuesOfCorrectType, ' + query InvalidItem($a: [String] = ["one", 2]) { + dog { name } + } + ', [ + $this->badValue('String', '2', 2, 50), ]); } } diff --git a/tests/Validator/VariablesDefaultValueAllowedTest.php b/tests/Validator/VariablesDefaultValueAllowedTest.php new file mode 100644 index 0000000..da4ca9b --- /dev/null +++ b/tests/Validator/VariablesDefaultValueAllowedTest.php @@ -0,0 +1,109 @@ +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), + ]); + } +}