From 012082d1d9bb767b2296ab3f9efb44fdae1ff796 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Tue, 27 Nov 2018 17:53:03 +0700 Subject: [PATCH 01/34] Minor documentation fix --- docs/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index c5c4e66..46dbb5a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,8 +44,8 @@ existing PHP frameworks, add support for Relay, etc. ## Current Status The first version of this library (v0.1) was released on August 10th 2015. -The current version (v0.10) supports all features described by GraphQL specification -(including April 2016 add-ons) as well as some experimental features like +The current version supports all features described by GraphQL specification +as well as some experimental features like [Schema Language parser](type-system/type-language.md) and [Schema printer](reference.md#graphqlutilsschemaprinter). From bf471838ae6a423ef5f2f170595070d5ed579b21 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Tue, 27 Nov 2018 18:22:01 +0700 Subject: [PATCH 02/34] Added all possible scalar types to Node constructor (cherry picked from commit ead1b864bc82e4ede561ede0d3e9085d4fac3ca0) --- src/Language/AST/Node.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/AST/Node.php b/src/Language/AST/Node.php index 826478b..2205d6d 100644 --- a/src/Language/AST/Node.php +++ b/src/Language/AST/Node.php @@ -40,7 +40,7 @@ abstract class Node public $loc; /** - * @param (string|NameNode|NodeList|SelectionSetNode|Location|null)[] $vars + * @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars */ public function __construct(array $vars) { From 16d42dead3ae990bdd03d01c17dca6db3a8d23cc Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Tue, 27 Nov 2018 19:39:41 +0700 Subject: [PATCH 03/34] Document BC and fix types in ResolveInfo (cherry picked from commit 376e9275054f9b63a27b8fa2215553f050898602) --- UPGRADE.md | 3 +++ src/Type/Definition/ResolveInfo.php | 32 ++++++++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 943f530..a9d7256 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -78,6 +78,9 @@ Parser::parse($source, [ 'allowLegacySDLImplementsInterfaces' => true]) - `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`) - `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`) +### Breaking: new constructors + +`GraphQL\Type\Definition\ResolveInfo` now takes 10 arguments instead of one array. ## Upgrade v0.11.x > v0.12.x diff --git a/src/Type/Definition/ResolveInfo.php b/src/Type/Definition/ResolveInfo.php index 8a752b1..377bb49 100644 --- a/src/Type/Definition/ResolveInfo.php +++ b/src/Type/Definition/ResolveInfo.php @@ -23,7 +23,7 @@ class ResolveInfo * The name of the field being resolved * * @api - * @var string|null + * @var string */ public $fieldName; @@ -31,9 +31,9 @@ class ResolveInfo * AST of all nodes referencing this field in the query. * * @api - * @var FieldNode[]|null + * @var FieldNode[] */ - public $fieldNodes; + public $fieldNodes = []; /** * Expected return type of the field being resolved @@ -47,7 +47,7 @@ class ResolveInfo * Parent type of the field being resolved * * @api - * @var ObjectType|null + * @var ObjectType */ public $parentType; @@ -55,7 +55,7 @@ class ResolveInfo * Path to this field from the very root value * * @api - * @var string[] + * @var string[][] */ public $path; @@ -71,9 +71,9 @@ class ResolveInfo * AST of all fragments defined in query * * @api - * @var FragmentDefinitionNode[]|null + * @var FragmentDefinitionNode[] */ - public $fragments; + public $fragments = []; /** * Root value passed to query execution @@ -95,21 +95,29 @@ class ResolveInfo * Array of variables passed to query execution * * @api - * @var mixed[]|null + * @var mixed[] */ - public $variableValues; + public $variableValues = []; + /** + * @param FieldNode[] $fieldNodes + * @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType + * @param string[][] $path + * @param FragmentDefinitionNode[] $fragments + * @param mixed|null $rootValue + * @param mixed[] $variableValues + */ public function __construct( string $fieldName, $fieldNodes, $returnType, ObjectType $parentType, - $path, + array $path, Schema $schema, - $fragments, + array $fragments, $rootValue, ?OperationDefinitionNode $operation, - $variableValues + array $variableValues ) { $this->fieldName = $fieldName; $this->fieldNodes = $fieldNodes; From 21dc3fe664fe56f803ad2be4e1a284ad26910bb3 Mon Sep 17 00:00:00 2001 From: Yury Date: Wed, 28 Nov 2018 21:35:21 +0700 Subject: [PATCH 04/34] Error handling improvements (cherry picked from commit 33e3c9c338b3c77edcdc009d546015e8d74ae049) --- src/Validator/Rules/ValuesOfCorrectType.php | 96 ++++++++++++-------- tests/Type/EnumTypeTest.php | 10 +-- tests/Validator/ValidationTest.php | 2 +- tests/Validator/ValuesOfCorrectTypeTest.php | 97 +++++++++------------ 4 files changed, 109 insertions(+), 96 deletions(-) diff --git a/src/Validator/Rules/ValuesOfCorrectType.php b/src/Validator/Rules/ValuesOfCorrectType.php index a08d99b..ad2468e 100644 --- a/src/Validator/Rules/ValuesOfCorrectType.php +++ b/src/Validator/Rules/ValuesOfCorrectType.php @@ -8,6 +8,7 @@ use Exception; use GraphQL\Error\Error; use GraphQL\Language\AST\BooleanValueNode; use GraphQL\Language\AST\EnumValueNode; +use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FloatValueNode; use GraphQL\Language\AST\IntValueNode; use GraphQL\Language\AST\ListValueNode; @@ -21,6 +22,7 @@ use GraphQL\Language\Printer; use GraphQL\Language\Visitor; use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumValueDefinition; +use GraphQL\Type\Definition\FieldArgument; use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NonNull; @@ -46,8 +48,14 @@ class ValuesOfCorrectType extends ValidationRule { public function getVisitor(ValidationContext $context) { + $fieldName = ''; return [ - NodeKind::NULL => static function (NullValueNode $node) use ($context) { + NodeKind::FIELD => [ + 'enter' => static function (FieldNode $node) use (&$fieldName) { + $fieldName = $node->name->value; + }, + ], + NodeKind::NULL => static function (NullValueNode $node) use ($context, &$fieldName) { $type = $context->getInputType(); if (! ($type instanceof NonNull)) { return; @@ -55,30 +63,31 @@ class ValuesOfCorrectType extends ValidationRule $context->reportError( new Error( - self::badValueMessage((string) $type, Printer::doPrint($node)), + self::getBadValueMessage((string) $type, Printer::doPrint($node), null, $context, $fieldName), $node ) ); }, - NodeKind::LST => function (ListValueNode $node) use ($context) { + NodeKind::LST => function (ListValueNode $node) use ($context, &$fieldName) { // 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); + $this->isValidScalar($context, $node, $fieldName); return Visitor::skipNode(); } }, - NodeKind::OBJECT => function (ObjectValueNode $node) use ($context) { + NodeKind::OBJECT => function (ObjectValueNode $node) use ($context, &$fieldName) { // 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); + $this->isValidScalar($context, $node, $fieldName); return Visitor::skipNode(); } + unset($fieldName); // Ensure every required field exists. $inputFields = $type->getFields(); $nodeFields = iterator_to_array($node->fields); @@ -127,34 +136,36 @@ class ValuesOfCorrectType extends ValidationRule ) ); }, - NodeKind::ENUM => function (EnumValueNode $node) use ($context) { + NodeKind::ENUM => function (EnumValueNode $node) use ($context, &$fieldName) { $type = Type::getNamedType($context->getInputType()); if (! $type instanceof EnumType) { - $this->isValidScalar($context, $node); + $this->isValidScalar($context, $node, $fieldName); } elseif (! $type->getValue($node->value)) { $context->reportError( new Error( - self::badValueMessage( + self::getBadValueMessage( $type->name, Printer::doPrint($node), - $this->enumTypeSuggestion($type, $node) + $this->enumTypeSuggestion($type, $node), + $context, + $fieldName ), $node ) ); } }, - NodeKind::INT => function (IntValueNode $node) use ($context) { - $this->isValidScalar($context, $node); + NodeKind::INT => function (IntValueNode $node) use ($context, &$fieldName) { + $this->isValidScalar($context, $node, $fieldName); }, - NodeKind::FLOAT => function (FloatValueNode $node) use ($context) { - $this->isValidScalar($context, $node); + NodeKind::FLOAT => function (FloatValueNode $node) use ($context, &$fieldName) { + $this->isValidScalar($context, $node, $fieldName); }, - NodeKind::STRING => function (StringValueNode $node) use ($context) { - $this->isValidScalar($context, $node); + NodeKind::STRING => function (StringValueNode $node) use ($context, &$fieldName) { + $this->isValidScalar($context, $node, $fieldName); }, - NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context) { - $this->isValidScalar($context, $node); + NodeKind::BOOLEAN => function (BooleanValueNode $node) use ($context, &$fieldName) { + $this->isValidScalar($context, $node, $fieldName); }, ]; } @@ -165,7 +176,7 @@ class ValuesOfCorrectType extends ValidationRule ($message ? "; ${message}" : '.'); } - private function isValidScalar(ValidationContext $context, ValueNode $node) + private function isValidScalar(ValidationContext $context, ValueNode $node, $fieldName) { // Report any error at the full type expected by the location. $locationType = $context->getInputType(); @@ -179,10 +190,12 @@ class ValuesOfCorrectType extends ValidationRule if (! $type instanceof ScalarType) { $context->reportError( new Error( - self::badValueMessage( + self::getBadValueMessage( (string) $locationType, Printer::doPrint($node), - $this->enumTypeSuggestion($type, $node) + $this->enumTypeSuggestion($type, $node), + $context, + $fieldName ), $node ) @@ -199,32 +212,28 @@ class ValuesOfCorrectType extends ValidationRule // Ensure a reference to the original error is maintained. $context->reportError( new Error( - self::badValueMessage( + self::getBadValueMessage( (string) $locationType, Printer::doPrint($node), - $error->getMessage() + $error->getMessage(), + $context, + $fieldName ), - $node, - null, - null, - null, - $error + $node ) ); } catch (Throwable $error) { // Ensure a reference to the original error is maintained. $context->reportError( new Error( - self::badValueMessage( + self::getBadValueMessage( (string) $locationType, Printer::doPrint($node), - $error->getMessage() + $error->getMessage(), + $context, + $fieldName ), - $node, - null, - null, - null, - $error + $node ) ); } @@ -247,6 +256,12 @@ class ValuesOfCorrectType extends ValidationRule } } + public static function badArgumentValueMessage($typeName, $valueName, $fieldName, $argName, $message = null) + { + return sprintf('Field "%s" argument "%s" requires type %s, found %s', $fieldName, $argName, $typeName, $valueName) . + ($message ? sprintf('; %s', $message) : '.'); + } + public static function requiredFieldMessage($typeName, $fieldName, $fieldTypeName) { return sprintf('Field %s.%s of required type %s was not provided.', $typeName, $fieldName, $fieldTypeName); @@ -257,4 +272,15 @@ class ValuesOfCorrectType extends ValidationRule return sprintf('Field "%s" is not defined by type %s', $fieldName, $typeName) . ($message ? sprintf('; %s', $message) : '.'); } + + private static function getBadValueMessage($typeName, $valueName, $message = null, $context = null, $fieldName = null) + { + if ($context) { + $arg = $context->getArgument(); + if ($arg) { + return self::badArgumentValueMessage($typeName, $valueName, $fieldName, $arg->name, $message); + } + } + return self::badValueMessage($typeName, $valueName, $message); + } } diff --git a/tests/Type/EnumTypeTest.php b/tests/Type/EnumTypeTest.php index df920e2..9b2e8e6 100644 --- a/tests/Type/EnumTypeTest.php +++ b/tests/Type/EnumTypeTest.php @@ -231,7 +231,7 @@ class EnumTypeTest extends TestCase '{ colorEnum(fromEnum: "GREEN") }', null, [ - 'message' => 'Expected type Color, found "GREEN"; Did you mean the enum value GREEN?', + 'message' => 'Field "colorEnum" argument "fromEnum" requires type Color, found "GREEN"; Did you mean the enum value GREEN?', 'locations' => [new SourceLocation(1, 23)], ] ); @@ -268,7 +268,7 @@ class EnumTypeTest extends TestCase '{ colorEnum(fromEnum: GREENISH) }', null, [ - 'message' => 'Expected type Color, found GREENISH; Did you mean the enum value GREEN?', + 'message' => 'Field "colorEnum" argument "fromEnum" requires type Color, found GREENISH; Did you mean the enum value GREEN?', 'locations' => [new SourceLocation(1, 23)], ] ); @@ -283,7 +283,7 @@ class EnumTypeTest extends TestCase '{ colorEnum(fromEnum: green) }', null, [ - 'message' => 'Expected type Color, found green; Did you mean the enum value GREEN?', + 'message' => 'Field "colorEnum" argument "fromEnum" requires type Color, found green; Did you mean the enum value GREEN?', 'locations' => [new SourceLocation(1, 23)], ] ); @@ -313,7 +313,7 @@ class EnumTypeTest extends TestCase $this->expectFailure( '{ colorEnum(fromEnum: 1) }', null, - 'Expected type Color, found 1.' + 'Field "colorEnum" argument "fromEnum" requires type Color, found 1.' ); } @@ -325,7 +325,7 @@ class EnumTypeTest extends TestCase $this->expectFailure( '{ colorEnum(fromInt: GREEN) }', null, - 'Expected type Int, found GREEN.' + 'Field "colorEnum" argument "fromInt" requires type Int, found GREEN.' ); } diff --git a/tests/Validator/ValidationTest.php b/tests/Validator/ValidationTest.php index 05dbbf2..e823cdc 100644 --- a/tests/Validator/ValidationTest.php +++ b/tests/Validator/ValidationTest.php @@ -38,7 +38,7 @@ class ValidationTest extends ValidatorTestCase '; $expectedError = [ - 'message' => 'Expected type Invalid, found "bad value"; Invalid scalar is always invalid: bad value', + 'message' => 'Field "invalidArg" argument "arg" requires type Invalid, found "bad value"; Invalid scalar is always invalid: bad value', 'locations' => [['line' => 3, 'column' => 25]], ]; diff --git a/tests/Validator/ValuesOfCorrectTypeTest.php b/tests/Validator/ValuesOfCorrectTypeTest.php index 4de4df4..a174808 100644 --- a/tests/Validator/ValuesOfCorrectTypeTest.php +++ b/tests/Validator/ValuesOfCorrectTypeTest.php @@ -240,7 +240,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', '1', 4, 39), + $this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found 1.', 4, 39), ] ); } @@ -257,6 +257,11 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase ); } + private function badValueWithMessage($message, $line, $column) + { + return FormattedError::create($message, [new SourceLocation($line, $column)]); + } + /** * @see it('Float into String') */ @@ -272,7 +277,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', '1.0', 4, 39), + $this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found 1.0.', 4, 39), ] ); } @@ -294,7 +299,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', 'true', 4, 39), + $this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found true.', 4, 39), ] ); } @@ -314,7 +319,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', 'BAR', 4, 39), + $this->badValueWithMessage('Field "stringArgField" argument "stringArg" requires type String, found BAR.', 4, 39), ] ); } @@ -334,7 +339,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', '"3"', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found "3".', 4, 33), ] ); } @@ -354,7 +359,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', '829384293849283498239482938', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found 829384293849283498239482938.', 4, 33), ] ); } @@ -376,7 +381,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', 'FOO', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found FOO.', 4, 33), ] ); } @@ -396,7 +401,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', '3.0', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found 3.0.', 4, 33), ] ); } @@ -416,7 +421,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int', '3.333', 4, 33), + $this->badValueWithMessage('Field "intArgField" argument "intArg" requires type Int, found 3.333.', 4, 33), ] ); } @@ -436,7 +441,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Float', '"3.333"', 4, 37), + $this->badValueWithMessage('Field "floatArgField" argument "floatArg" requires type Float, found "3.333".', 4, 37), ] ); } @@ -456,7 +461,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Float', 'true', 4, 37), + $this->badValueWithMessage('Field "floatArgField" argument "floatArg" requires type Float, found true.', 4, 37), ] ); } @@ -478,7 +483,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Float', 'FOO', 4, 37), + $this->badValueWithMessage('Field "floatArgField" argument "floatArg" requires type Float, found FOO.', 4, 37), ] ); } @@ -498,7 +503,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean', '2', 4, 41), + $this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found 2.', 4, 41), ] ); } @@ -518,7 +523,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean', '1.0', 4, 41), + $this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found 1.0.', 4, 41), ] ); } @@ -540,7 +545,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean', '"true"', 4, 41), + $this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found "true".', 4, 41), ] ); } @@ -560,7 +565,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean', 'TRUE', 4, 41), + $this->badValueWithMessage('Field "booleanArgField" argument "booleanArg" requires type Boolean, found TRUE.', 4, 41), ] ); } @@ -580,7 +585,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('ID', '1.0', 4, 31), + $this->badValueWithMessage('Field "idArgField" argument "idArg" requires type ID, found 1.0.', 4, 31), ] ); } @@ -600,7 +605,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('ID', 'true', 4, 31), + $this->badValueWithMessage('Field "idArgField" argument "idArg" requires type ID, found true.', 4, 31), ] ); } @@ -622,7 +627,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('ID', 'SOMETHING', 4, 31), + $this->badValueWithMessage('Field "idArgField" argument "idArg" requires type ID, found SOMETHING.', 4, 31), ] ); } @@ -642,7 +647,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('DogCommand', '2', 4, 41), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found 2.', 4, 41), ] ); } @@ -662,7 +667,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('DogCommand', '1.0', 4, 41), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found 1.0.', 4, 41), ] ); } @@ -684,13 +689,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue( - 'DogCommand', - '"SIT"', - 4, - 41, - 'Did you mean the enum value SIT?' - ), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found "SIT"; Did you mean the enum value SIT?', 4, 41), ] ); } @@ -710,7 +709,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('DogCommand', 'true', 4, 41), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found true.', 4, 41), ] ); } @@ -730,7 +729,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('DogCommand', 'JUGGLE', 4, 41), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found JUGGLE.', 4, 41), ] ); } @@ -750,13 +749,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue( - 'DogCommand', - 'sit', - 4, - 41, - 'Did you mean the enum value SIT?' - ), + $this->badValueWithMessage('Field "doesKnowCommand" argument "dogCommand" requires type DogCommand, found sit; Did you mean the enum value SIT?', 4, 41), ] ); } @@ -846,7 +839,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', '2', 4, 55), + $this->badValueWithMessage('Field "stringListArgField" argument "stringListArg" requires type String, found 2.', 4, 55), ] ); } @@ -866,7 +859,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('[String]', '1', 4, 47), + $this->badValueWithMessage('Field "stringListArgField" argument "stringListArg" requires type [String], found 1.', 4, 47), ] ); } @@ -1060,8 +1053,8 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int!', '"two"', 4, 32), - $this->badValue('Int!', '"one"', 4, 45), + $this->badValueWithMessage('Field "multipleReqs" argument "req2" requires type Int!, found "two".', 4, 32), + $this->badValueWithMessage('Field "multipleReqs" argument "req1" requires type Int!, found "one".', 4, 45), ] ); } @@ -1081,7 +1074,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int!', '"one"', 4, 32), + $this->badValueWithMessage('Field "multipleReqs" argument "req1" requires type Int!, found "one".', 4, 32), ] ); } @@ -1103,7 +1096,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Int!', 'null', 4, 32), + $this->badValueWithMessage('Field "multipleReqs" argument "req1" requires type Int!, found null.', 4, 32), ] ); } @@ -1277,7 +1270,7 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('String', '2', 5, 40), + $this->badValueWithMessage('Field "complexArgField" argument "complexArg" requires type String, found 2.', 5, 40), ] ); } @@ -1340,19 +1333,13 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue( - 'Invalid', - '123', - 3, - 27, - 'Invalid scalar is always invalid: 123' - ), + $this->badValueWithMessage('Field "invalidArg" argument "arg" requires type Invalid, found 123; Invalid scalar is always invalid: 123', 3, 27), ] ); self::assertEquals( - 'Invalid scalar is always invalid: 123', - $errors[0]->getPrevious()->getMessage() + 'Field "invalidArg" argument "arg" requires type Invalid, found 123; Invalid scalar is always invalid: 123', + $errors[0]->getMessage() ); } @@ -1411,8 +1398,8 @@ class ValuesOfCorrectTypeTest extends ValidatorTestCase } ', [ - $this->badValue('Boolean!', '"yes"', 3, 28), - $this->badValue('Boolean!', 'ENUM', 4, 28), + $this->badValueWithMessage('Field "dog" argument "if" requires type Boolean!, found "yes".', 3, 28), + $this->badValueWithMessage('Field "name" argument "if" requires type Boolean!, found ENUM.', 4, 28), ] ); } From b005803bf62055be04e982813fb8b4f57dd6b0ba Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 29 Nov 2018 17:10:51 +0700 Subject: [PATCH 05/34] Add PHP 7.3 to Travis (cherry picked from commit f95d1e81eabfae0b83bba19f2b260b5d04640d4f) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 98acdaa..d27fa09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ language: php php: - 7.1 - 7.2 + - 7.3 - nightly env: From 1e778d259e0dcdf50be629f972fcdd089b8432ee Mon Sep 17 00:00:00 2001 From: chriszarate Date: Sun, 2 Dec 2018 06:49:23 +0700 Subject: [PATCH 06/34] Apollo server/client compatibility. Look for queryid in extensions. (cherry picked from commit 63b4e3f0a4527c491f51e37bbda99169a47690f1) --- src/Server/OperationParams.php | 5 +++++ tests/Server/RequestParsingTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index 8085d1e..a76f86e 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -95,6 +95,11 @@ class OperationParams $instance->variables = $params['variables']; $instance->readOnly = (bool) $readonly; + // Apollo server/client compatibility: look for the queryid in extensions + if (isset($params['extensions']['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryid)) { + $instance->queryId = $params['extensions']['persistedQuery']['sha256Hash']; + } + return $instance; } diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index b6c73d6..75412b1 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -333,6 +333,32 @@ class RequestParsingTest extends TestCase } } + public function testParsesApolloPersistedQueryJSONRequest() : void + { + $queryId = 'my-query-id'; + $extensions = [ + 'persistedQuery' => [ + 'sha256Hash' => $queryId, + ], + ]; + $variables = ['test' => 1, 'test2' => 2]; + $operation = 'op'; + + $body = [ + 'extensions' => $extensions, + 'variables' => $variables, + 'operationName' => $operation, + ]; + $parsed = [ + 'raw' => $this->parseRawRequest('application/json', json_encode($body)), + 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), + ]; + foreach ($parsed as $method => $parsedBody) { + self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $method); + self::assertFalse($parsedBody->isReadOnly(), $method); + } + } + public function testParsesBatchJSONRequest() : void { $body = [ From 9ada6069193c7f2ffd63435d283b42590ac2333a Mon Sep 17 00:00:00 2001 From: chriszarate Date: Sun, 2 Dec 2018 07:43:51 +0700 Subject: [PATCH 07/34] Fix linting issue and typos. (cherry picked from commit c33e41f2bf44b20b3d79b00d335c3a5ea5975116) --- src/Server/OperationParams.php | 4 ++-- tests/Server/RequestParsingTest.php | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index a76f86e..65edfc8 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -96,8 +96,8 @@ class OperationParams $instance->readOnly = (bool) $readonly; // Apollo server/client compatibility: look for the queryid in extensions - if (isset($params['extensions']['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryid)) { - $instance->queryId = $params['extensions']['persistedQuery']['sha256Hash']; + if (isset($params['extensions']['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) { + $instance->queryId = $params['extensions']['persistedQuery']['sha256Hash']; } return $instance; diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index 75412b1..f935281 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -335,12 +335,8 @@ class RequestParsingTest extends TestCase public function testParsesApolloPersistedQueryJSONRequest() : void { - $queryId = 'my-query-id'; - $extensions = [ - 'persistedQuery' => [ - 'sha256Hash' => $queryId, - ], - ]; + $queryId = 'my-query-id'; + $extensions = ['persistedQuery' => ['sha256Hash' => $queryId]]; $variables = ['test' => 1, 'test2' => 2]; $operation = 'op'; From a116127436bc533d3d1cf33c567f4fdc06eb31c7 Mon Sep 17 00:00:00 2001 From: chriszarate Date: Mon, 3 Dec 2018 03:10:09 +0700 Subject: [PATCH 08/34] Add extensions to OperationParams instance. (cherry picked from commit f644c1a837890e4ca854d16371c0730bc32b9314) --- src/Server/OperationParams.php | 22 +++++++++++++++------- tests/Server/RequestParsingTest.php | 20 ++++++++++++-------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index 65edfc8..0d14614 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -46,6 +46,12 @@ class OperationParams */ public $variables; + /** + * @api + * @var mixed[]|null + */ + public $extensions; + /** @var mixed[] */ private $originalInput; @@ -76,6 +82,7 @@ class OperationParams 'id' => null, // alias to queryid 'operationname' => null, 'variables' => null, + 'extensions' => null, ]; if ($params['variables'] === '') { @@ -89,15 +96,16 @@ class OperationParams } } - $instance->query = $params['query']; - $instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id']; - $instance->operation = $params['operationname']; - $instance->variables = $params['variables']; - $instance->readOnly = (bool) $readonly; + $instance->query = $params['query']; + $instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id']; + $instance->operation = $params['operationname']; + $instance->variables = $params['variables']; + $instance->extensions = $params['extensions']; + $instance->readOnly = (bool) $readonly; // Apollo server/client compatibility: look for the queryid in extensions - if (isset($params['extensions']['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) { - $instance->queryId = $params['extensions']['persistedQuery']['sha256Hash']; + if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) { + $instance->queryId = $instance->extensions['persistedQuery']['sha256Hash']; } return $instance; diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index f935281..cf2e9c5 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -25,7 +25,7 @@ class RequestParsingTest extends TestCase ]; foreach ($parsed as $source => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, null, null, $source); + self::assertValidOperationParams($parsedBody, $query, null, null, null, null, $source); self::assertFalse($parsedBody->isReadOnly(), $source); } } @@ -91,6 +91,7 @@ class RequestParsingTest extends TestCase $queryId = null, $variables = null, $operation = null, + $extensions = null, $message = '' ) { self::assertInstanceOf(OperationParams::class, $params, $message); @@ -99,6 +100,7 @@ class RequestParsingTest extends TestCase self::assertSame($queryId, $params->queryId, $message); self::assertSame($variables, $params->variables, $message); self::assertSame($operation, $params->operation, $message); + self::assertSame($extensions, $params->extensions, $message); } public function testParsesUrlencodedRequest() : void @@ -118,7 +120,7 @@ class RequestParsingTest extends TestCase ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -175,7 +177,7 @@ class RequestParsingTest extends TestCase ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertTrue($parsedBody->isReadonly(), $method); } } @@ -230,7 +232,7 @@ class RequestParsingTest extends TestCase ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -286,7 +288,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -307,7 +309,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -328,7 +330,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -350,7 +352,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $method); + self::assertValidOperationParams($parsedBody, null, $queryId, $variables, $operation, $extensions, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } @@ -382,6 +384,7 @@ class RequestParsingTest extends TestCase null, $body[0]['variables'], $body[0]['operationName'], + null, $method ); self::assertValidOperationParams( @@ -390,6 +393,7 @@ class RequestParsingTest extends TestCase $body[1]['queryId'], $body[1]['variables'], $body[1]['operationName'], + null, $method ); } From 59c128c54a22060b5899f91ed07dc841afb5d6da Mon Sep 17 00:00:00 2001 From: Erik Gaal Date: Wed, 5 Dec 2018 17:42:17 +0700 Subject: [PATCH 09/34] Add NullableType interface (cherry picked from commit 9a0dbff26b72cca1528ba4fac536fafc7ba3f624) --- src/Type/Definition/EnumType.php | 2 +- src/Type/Definition/InputObjectType.php | 2 +- src/Type/Definition/InterfaceType.php | 2 +- src/Type/Definition/ListOfType.php | 2 +- src/Type/Definition/NonNull.php | 14 ++++---------- src/Type/Definition/NullableType.php | 20 ++++++++++++++++++++ src/Type/Definition/ObjectType.php | 2 +- src/Type/Definition/ScalarType.php | 2 +- src/Type/Definition/Type.php | 4 ++-- src/Type/Definition/UnionType.php | 2 +- 10 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 src/Type/Definition/NullableType.php diff --git a/src/Type/Definition/EnumType.php b/src/Type/Definition/EnumType.php index 2b42590..b565a25 100644 --- a/src/Type/Definition/EnumType.php +++ b/src/Type/Definition/EnumType.php @@ -22,7 +22,7 @@ use function sprintf; /** * Class EnumType */ -class EnumType extends Type implements InputType, OutputType, LeafType, NamedType +class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType { /** @var EnumTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/InputObjectType.php b/src/Type/Definition/InputObjectType.php index e43caad..756e142 100644 --- a/src/Type/Definition/InputObjectType.php +++ b/src/Type/Definition/InputObjectType.php @@ -18,7 +18,7 @@ use function sprintf; /** * Class InputObjectType */ -class InputObjectType extends Type implements InputType, NamedType +class InputObjectType extends Type implements InputType, NullableType, NamedType { /** @var InputObjectTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/InterfaceType.php b/src/Type/Definition/InterfaceType.php index ff01a48..52ad81d 100644 --- a/src/Type/Definition/InterfaceType.php +++ b/src/Type/Definition/InterfaceType.php @@ -15,7 +15,7 @@ use function sprintf; /** * Class InterfaceType */ -class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NamedType +class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType { /** @var InterfaceTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/ListOfType.php b/src/Type/Definition/ListOfType.php index 7fdb6e6..29e3438 100644 --- a/src/Type/Definition/ListOfType.php +++ b/src/Type/Definition/ListOfType.php @@ -7,7 +7,7 @@ namespace GraphQL\Type\Definition; /** * Class ListOfType */ -class ListOfType extends Type implements WrappingType, OutputType, InputType +class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType { /** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */ public $ofType; diff --git a/src/Type/Definition/NonNull.php b/src/Type/Definition/NonNull.php index f3ebbca..cc80be0 100644 --- a/src/Type/Definition/NonNull.php +++ b/src/Type/Definition/NonNull.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace GraphQL\Type\Definition; -use Exception; -use GraphQL\Error\InvariantViolation; use GraphQL\Utils\Utils; /** @@ -13,13 +11,11 @@ use GraphQL\Utils\Utils; */ class NonNull extends Type implements WrappingType, OutputType, InputType { - /** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType */ + /** @var NullableType */ private $ofType; /** - * @param callable|Type $type - * - * @throws Exception + * @param NullableType $type */ public function __construct($type) { @@ -29,7 +25,7 @@ class NonNull extends Type implements WrappingType, OutputType, InputType /** * @param mixed $type * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType + * @return NullableType */ public static function assertNullableType($type) { @@ -67,9 +63,7 @@ class NonNull extends Type implements WrappingType, OutputType, InputType /** * @param bool $recurse * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType - * - * @throws InvariantViolation + * @return Type */ public function getWrappedType($recurse = false) { diff --git a/src/Type/Definition/NullableType.php b/src/Type/Definition/NullableType.php new file mode 100644 index 0000000..7ff70b2 --- /dev/null +++ b/src/Type/Definition/NullableType.php @@ -0,0 +1,20 @@ +; + */ + +interface NullableType +{ +} diff --git a/src/Type/Definition/ObjectType.php b/src/Type/Definition/ObjectType.php index ac40258..f509293 100644 --- a/src/Type/Definition/ObjectType.php +++ b/src/Type/Definition/ObjectType.php @@ -54,7 +54,7 @@ use function sprintf; * } * ]); */ -class ObjectType extends Type implements OutputType, CompositeType, NamedType +class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType { /** @var ObjectTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/ScalarType.php b/src/Type/Definition/ScalarType.php index db4e63f..17bf6e7 100644 --- a/src/Type/Definition/ScalarType.php +++ b/src/Type/Definition/ScalarType.php @@ -27,7 +27,7 @@ use function is_string; * } * } */ -abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NamedType +abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NullableType, NamedType { /** @var ScalarTypeDefinitionNode|null */ public $astNode; diff --git a/src/Type/Definition/Type.php b/src/Type/Definition/Type.php index c349d11..5b8e414 100644 --- a/src/Type/Definition/Type.php +++ b/src/Type/Definition/Type.php @@ -137,7 +137,7 @@ abstract class Type implements JsonSerializable } /** - * @param ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType $wrappedType + * @param NullableType $wrappedType * * @return NonNull * @@ -338,7 +338,7 @@ abstract class Type implements JsonSerializable /** * @param Type $type * - * @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType + * @return NullableType * * @api */ diff --git a/src/Type/Definition/UnionType.php b/src/Type/Definition/UnionType.php index a8e6fd7..8a735e3 100644 --- a/src/Type/Definition/UnionType.php +++ b/src/Type/Definition/UnionType.php @@ -17,7 +17,7 @@ use function sprintf; /** * Class UnionType */ -class UnionType extends Type implements AbstractType, OutputType, CompositeType, NamedType +class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType { /** @var UnionTypeDefinitionNode */ public $astNode; From 22cee4974765e91218f786fe07eb0a520e355dee Mon Sep 17 00:00:00 2001 From: Torsten Blindert Date: Thu, 6 Dec 2018 00:55:14 +0700 Subject: [PATCH 10/34] BUGFIX: expect ->getType() to throw (cherry picked from commit 62b003643779246403c0fd71d7e7486d94c50835) --- src/Utils/SchemaExtender.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php index fab006e..110e750 100644 --- a/src/Utils/SchemaExtender.php +++ b/src/Utils/SchemaExtender.php @@ -513,7 +513,14 @@ class SchemaExtender $schemaExtensions[] = $def; } elseif ($def instanceof TypeDefinitionNode) { $typeName = isset($def->name) ? $def->name->value : null; - if ($schema->getType($typeName)) { + + try { + $type = $schema->getType($typeName); + } catch (Error $error) { + $type = null; + } + + if ($type) { throw new Error('Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.', [$def]); } $typeDefinitionMap[$typeName] = $def; From fda73f321221e54446238a4499aa477f392aff64 Mon Sep 17 00:00:00 2001 From: Torsten Blindert Date: Thu, 6 Dec 2018 01:13:32 +0700 Subject: [PATCH 11/34] TASK: Added test (cherry picked from commit d5fddfd504d438d92a32be28546a615cc49f3c6f) --- tests/Utils/SchemaExtenderTest.php | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index 8965fa7..be07d1c 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -24,6 +24,7 @@ use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Schema; +use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaExtender; use GraphQL\Utils\SchemaPrinter; use PHPUnit\Framework\TestCase; @@ -1962,4 +1963,48 @@ extend type Query { $result = GraphQL::executeQuery($extendedSchema, $query); self::assertSame(['data' => ['hello' => 'Hello World!']], $result->toArray()); } + + + /** + * @see https://github.com/webonyx/graphql-php/issues/180 + */ + public function testShouldBeAbleToIntroduceNewTypesThroughExtension() + { + $sdl = ' + type Query { + defaultValue: String + } + type Foo { + value: Int + } + '; + + $documentNode = Parser::parse($sdl); + $schema = BuildSchema::build($documentNode); + + $extensionSdl = ' + type Bar { + foo: Foo + } + '; + + $extendedDocumentNode = Parser::parse($extensionSdl); + $extendedSchema = SchemaExtender::extend($schema, $extendedDocumentNode); + + $expected = ' + type Bar { + foo: Foo + } + + type Foo { + value: Int + } + + type Query { + defaultValue: String + } + '; + + static::assertEquals($this->dedent($expected), SchemaPrinter::doPrint($extendedSchema)); + } } From 1d8f526d91e7ed0929867e14f81b6e6a933e6958 Mon Sep 17 00:00:00 2001 From: Torsten Blindert Date: Thu, 6 Dec 2018 01:23:07 +0700 Subject: [PATCH 12/34] TASK: Code style (cherry picked from commit 9609d2ac84623291c76c4c72b61235799ad07f5e) --- tests/Utils/SchemaExtenderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php index be07d1c..bc9842d 100644 --- a/tests/Utils/SchemaExtenderTest.php +++ b/tests/Utils/SchemaExtenderTest.php @@ -1980,7 +1980,7 @@ extend type Query { '; $documentNode = Parser::parse($sdl); - $schema = BuildSchema::build($documentNode); + $schema = BuildSchema::build($documentNode); $extensionSdl = ' type Bar { @@ -1989,7 +1989,7 @@ extend type Query { '; $extendedDocumentNode = Parser::parse($extensionSdl); - $extendedSchema = SchemaExtender::extend($schema, $extendedDocumentNode); + $extendedSchema = SchemaExtender::extend($schema, $extendedDocumentNode); $expected = ' type Bar { From 08992de960e831329303982eb8cb5611ee024a35 Mon Sep 17 00:00:00 2001 From: chriszarate Date: Wed, 12 Dec 2018 10:58:57 +0700 Subject: [PATCH 13/34] Allow extensions to be provided in GET request. (cherry picked from commit 244ec66ecc0677d25ad2d958baed7de50e551043) --- src/Server/OperationParams.php | 15 +++++++++++---- tests/Server/RequestParsingTest.php | 12 +++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Server/OperationParams.php b/src/Server/OperationParams.php index 0d14614..6d2cfd1 100644 --- a/src/Server/OperationParams.php +++ b/src/Server/OperationParams.php @@ -89,11 +89,18 @@ class OperationParams $params['variables'] = null; } - if (is_string($params['variables'])) { - $tmp = json_decode($params['variables'], true); - if (! json_last_error()) { - $params['variables'] = $tmp; + // Some parameters could be provided as serialized JSON. + foreach (['extensions', 'variables'] as $param) { + if (! is_string($params[$param])) { + continue; } + + $tmp = json_decode($params[$param], true); + if (json_last_error()) { + continue; + } + + $params[$param] = $tmp; } $instance->query = $params['query']; diff --git a/tests/Server/RequestParsingTest.php b/tests/Server/RequestParsingTest.php index cf2e9c5..9de6eab 100644 --- a/tests/Server/RequestParsingTest.php +++ b/tests/Server/RequestParsingTest.php @@ -293,14 +293,16 @@ class RequestParsingTest extends TestCase } } - public function testParsesVariablesAsJSON() : void + public function testParsesParamsAsJSON() : void { - $query = '{my query}'; - $variables = ['test' => 1, 'test2' => 2]; - $operation = 'op'; + $query = '{my query}'; + $variables = ['test1' => 1, 'test2' => 2]; + $extensions = ['test3' => 3, 'test4' => 4]; + $operation = 'op'; $body = [ 'query' => $query, + 'extensions' => json_encode($extensions), 'variables' => json_encode($variables), 'operationName' => $operation, ]; @@ -309,7 +311,7 @@ class RequestParsingTest extends TestCase 'psr' => $this->parsePsrRequest('application/json', json_encode($body)), ]; foreach ($parsed as $method => $parsedBody) { - self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, null, $method); + self::assertValidOperationParams($parsedBody, $query, null, $variables, $operation, $extensions, $method); self::assertFalse($parsedBody->isReadOnly(), $method); } } From 42b20e76517167c056848069a96ed2dcad27a826 Mon Sep 17 00:00:00 2001 From: Jan Bukva Date: Thu, 27 Dec 2018 20:48:03 +0700 Subject: [PATCH 14/34] Failing test for CoroutineExecutor removes associative array when using custom scalar producing JSON (cherry picked from commit 31d89acfae5a27b772cd0ebec992d0e564796cf5) --- tests/Executor/ExecutorSchemaTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index 4c3b9d6..36a2a73 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -6,6 +6,7 @@ namespace GraphQL\Tests\Executor; use GraphQL\Executor\Executor; use GraphQL\Language\Parser; +use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; @@ -20,6 +21,13 @@ class ExecutorSchemaTest extends TestCase */ public function testExecutesUsingASchema() : void { + $BlogSerializableValueType = new CustomScalarType([ + 'name' => 'JsonSerializableValueScalar', + 'serialize' => static function ($value) { return $value; }, + 'parseValue' => static function ($value) { return $value; }, + 'parseLiteral' => static function ($value) { return $value; }, + ]); + $BlogArticle = null; $BlogImage = new ObjectType([ 'name' => 'Image', @@ -57,6 +65,7 @@ class ExecutorSchemaTest extends TestCase 'title' => ['type' => Type::string()], 'body' => ['type' => Type::string()], 'keywords' => ['type' => Type::listOf(Type::string())], + 'meta' => ['type' => $BlogSerializableValueType], ], ]); @@ -113,6 +122,7 @@ class ExecutorSchemaTest extends TestCase keywords } } + meta } } @@ -191,6 +201,9 @@ class ExecutorSchemaTest extends TestCase 'keywords' => ['foo', 'bar', '1', 'true', null], ], ], + 'meta' => [ + 'title' => 'My Article 1 | My Blog' + ] ], ], ]; @@ -210,6 +223,9 @@ class ExecutorSchemaTest extends TestCase 'body' => 'This is a post', 'hidden' => 'This data is not exposed in the schema', 'keywords' => ['foo', 'bar', 1, true, null], + 'meta' => [ + 'title' => 'My Article 1 | My Blog', + ], ]; }; From 8b8ea0d4a387aca74489d8093d1ee5f2062e4508 Mon Sep 17 00:00:00 2001 From: Jan Bukva Date: Thu, 27 Dec 2018 22:00:57 +0700 Subject: [PATCH 15/34] Fix CoroutineExecutor::resultToArray for associative arrays (cherry picked from commit 2295b96a493b9ce6528abd8e1a644e1164bb9fbe) --- src/Experimental/Executor/CoroutineExecutor.php | 14 +++++++++++--- tests/Executor/ExecutorSchemaTest.php | 14 +++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index b473c56..7d985c1 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -34,8 +34,11 @@ use GraphQL\Utils\Utils; use SplQueue; use stdClass; use Throwable; +use function array_keys; +use function count; use function is_array; use function is_string; +use function range; use function sprintf; class CoroutineExecutor implements Runtime, ExecutorImplementation @@ -151,9 +154,14 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation } if (is_array($value)) { - $array = []; - foreach ($value as $item) { - $array[] = self::resultToArray($item); + $array = []; + $isAssoc = array_keys($value) !== range(0, count($value) - 1); + foreach ($value as $key => $item) { + if ($isAssoc) { + $array[$key] = self::resultToArray($item); + } else { + $array[] = self::resultToArray($item); + } } return $array; } diff --git a/tests/Executor/ExecutorSchemaTest.php b/tests/Executor/ExecutorSchemaTest.php index 36a2a73..4946c59 100644 --- a/tests/Executor/ExecutorSchemaTest.php +++ b/tests/Executor/ExecutorSchemaTest.php @@ -23,9 +23,9 @@ class ExecutorSchemaTest extends TestCase { $BlogSerializableValueType = new CustomScalarType([ 'name' => 'JsonSerializableValueScalar', - 'serialize' => static function ($value) { return $value; }, - 'parseValue' => static function ($value) { return $value; }, - 'parseLiteral' => static function ($value) { return $value; }, + 'serialize' => static function ($value) { + return $value; + }, ]); $BlogArticle = null; @@ -201,9 +201,7 @@ class ExecutorSchemaTest extends TestCase 'keywords' => ['foo', 'bar', '1', 'true', null], ], ], - 'meta' => [ - 'title' => 'My Article 1 | My Blog' - ] + 'meta' => [ 'title' => 'My Article 1 | My Blog' ], ], ], ]; @@ -223,9 +221,7 @@ class ExecutorSchemaTest extends TestCase 'body' => 'This is a post', 'hidden' => 'This data is not exposed in the schema', 'keywords' => ['foo', 'bar', 1, true, null], - 'meta' => [ - 'title' => 'My Article 1 | My Blog', - ], + 'meta' => ['title' => 'My Article 1 | My Blog'], ]; }; From f52dfcfaef1ab566fb30ba63c6c5cc9e2e1564ca Mon Sep 17 00:00:00 2001 From: Jan Bukva Date: Fri, 28 Dec 2018 03:53:16 +0700 Subject: [PATCH 16/34] Use key-value foreach (cherry picked from commit 00490d289c0a858842e08cead45635016f147921) --- src/Experimental/Executor/CoroutineExecutor.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index 7d985c1..b089fad 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -34,11 +34,8 @@ use GraphQL\Utils\Utils; use SplQueue; use stdClass; use Throwable; -use function array_keys; -use function count; use function is_array; use function is_string; -use function range; use function sprintf; class CoroutineExecutor implements Runtime, ExecutorImplementation @@ -154,14 +151,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation } if (is_array($value)) { - $array = []; - $isAssoc = array_keys($value) !== range(0, count($value) - 1); + $array = []; foreach ($value as $key => $item) { - if ($isAssoc) { - $array[$key] = self::resultToArray($item); - } else { - $array[] = self::resultToArray($item); - } + $array[$key] = self::resultToArray($item); } return $array; } From ababa18157048547a6b6adf3c16e1d28df6ea7b1 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 28 Dec 2018 04:39:16 +0700 Subject: [PATCH 17/34] Fix Deferred (cherry picked from commit 828a9fb002e975ddf5945d3f4af91e4e4e8f23e2) --- src/Deferred.php | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Deferred.php b/src/Deferred.php index 197eeeb..6b7bd78 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -11,7 +11,7 @@ use Throwable; class Deferred { - /** @var SplQueue */ + /** @var SplQueue|null */ private static $queue; /** @var callable */ @@ -20,21 +20,6 @@ class Deferred /** @var SyncPromise */ public $promise; - public static function getQueue() - { - return self::$queue ?: self::$queue = new SplQueue(); - } - - public static function runQueue() - { - $q = self::$queue; - while ($q && ! $q->isEmpty()) { - /** @var self $dfd */ - $dfd = $q->dequeue(); - $dfd->run(); - } - } - public function __construct(callable $callback) { $this->callback = $callback; @@ -42,6 +27,25 @@ class Deferred self::getQueue()->enqueue($this); } + public static function getQueue() : SplQueue + { + if (self::$queue === null) { + self::$queue = new SplQueue(); + } + + return self::$queue; + } + + public static function runQueue() : void + { + $queue = self::getQueue(); + while (! $queue->isEmpty()) { + /** @var self $dequeuedNodeValue */ + $dequeuedNodeValue = $queue->dequeue(); + $dequeuedNodeValue->run(); + } + } + public function then($onFulfilled = null, $onRejected = null) { return $this->promise->then($onFulfilled, $onRejected); From 610979555d590e0d5379cb92eeef7f5314226512 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sun, 30 Dec 2018 06:51:55 +0700 Subject: [PATCH 18/34] Merge clovers (cherry picked from commit fd7465521a2a6e75dc69c8547c8551c0afe8e60e) --- .scrutinizer.yml | 2 +- .travis.yml | 16 ++++++++++------ composer.json | 1 + 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index a211422..9cef7f5 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -20,7 +20,7 @@ build: tools: external_code_coverage: - timeout: 600 + timeout: 900 build_failure_conditions: - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed diff --git a/.travis.yml b/.travis.yml index d27fa09..6065add 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,10 @@ php: env: matrix: - - EXECUTOR=coroutine + - EXECUTOR= DEPENDENCIES=--prefer-lowest + - EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest - EXECUTOR= + - EXECUTOR=coroutine cache: @@ -31,9 +33,8 @@ jobs: include: - stage: Test - env: DEPENDENCIES=low install: - - travis_retry composer update --prefer-dist --prefer-lowest + - travis_retry composer update --prefer-dist {$DEPENDENCIES} - stage: Test env: COVERAGE @@ -41,10 +42,12 @@ jobs: - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,} - if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi script: - - ./vendor/bin/phpunit --coverage-clover clover.xml + - ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor.cov + - EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor-coroutine.cov after_script: - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover clover.xml + - ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/clover.xml + - wget https://github.com/scrutinizer-ci/ocular/releases/download/1.5.2/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover /tmp/clover.xml - stage: Code Quality php: 7.1 @@ -58,3 +61,4 @@ jobs: env: STATIC_ANALYSIS install: travis_retry composer install --prefer-dist script: composer static-analysis + diff --git a/composer.json b/composer.json index 188e435..34f9d61 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "phpstan/phpstan": "0.10.5", "phpstan/phpstan-phpunit": "0.10.0", "phpstan/phpstan-strict-rules": "0.10.1", + "phpunit/phpcov": "^5.0", "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", "react/promise": "2.*" From 153f6f862e7f31ea11026eaff3907869319a9ae1 Mon Sep 17 00:00:00 2001 From: Jan Bukva Date: Tue, 29 Jan 2019 04:43:23 +0700 Subject: [PATCH 19/34] Fix return annotation of resolveType() in InterfaceType (cherry picked from commit edb52685839c12db3b50e51df035ee6ab5c6a67f) --- src/Type/Definition/InterfaceType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Definition/InterfaceType.php b/src/Type/Definition/InterfaceType.php index 52ad81d..1c7c389 100644 --- a/src/Type/Definition/InterfaceType.php +++ b/src/Type/Definition/InterfaceType.php @@ -107,7 +107,7 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT * @param object $objectValue * @param mixed[] $context * - * @return callable|null + * @return Type|null */ public function resolveType($objectValue, $context, ResolveInfo $info) { From a0f214a9f98762a706f5126d91204aa90318f356 Mon Sep 17 00:00:00 2001 From: Stefano Torresi Date: Mon, 4 Feb 2019 18:38:31 +0700 Subject: [PATCH 20/34] Update docs intro verbiage Just throwing my 2 cents: I don't think it's fair to say that "it's intended to be a replacement", given that multiple API paradigms can coexist in the same system and each of them have their trade-offs. (cherry picked from commit b1ab1820b684ac659dbdfd04ae2b0f502dbc0696) --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 46dbb5a..a031a42 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,7 +7,7 @@ # About GraphQL GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients. -It is intended to be a replacement for REST and SOAP APIs (even for **existing applications**). +It is intended to be an alternative to REST and SOAP APIs (even for **existing applications**). GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook engineers. Various implementations of this specification were written From c628fa39a1b7b61e8b30c8b502d22a30769a74ed Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Thu, 7 Feb 2019 03:14:11 +0700 Subject: [PATCH 21/34] Fix incorrect array type of contextValue in PHPDocs (cherry picked from commit 7d59811c4ff3134d1e4fd79652f8601e9bd37d36) --- src/Executor/Executor.php | 6 +++--- src/Executor/ReferenceExecutor.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index bf07cdd..c87558d 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -74,7 +74,7 @@ class Executor * execution are collected in `$result->errors`. * * @param mixed|null $rootValue - * @param mixed[]|null $contextValue + * @param mixed|null $contextValue * @param mixed[]|ArrayAccess|null $variableValues * @param string|null $operationName * @@ -120,7 +120,7 @@ class Executor * Useful for async PHP platforms. * * @param mixed[]|null $rootValue - * @param mixed[]|null $contextValue + * @param mixed|null $contextValue * @param mixed[]|null $variableValues * @param string|null $operationName * @@ -163,7 +163,7 @@ class Executor * * @param mixed $source * @param mixed[] $args - * @param mixed[]|null $context + * @param mixed|null $context * * @return mixed|null */ diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index 837628a..e61e5ea 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -116,7 +116,7 @@ class ReferenceExecutor implements ExecutorImplementation * execute, which we will pass throughout the other execution methods. * * @param mixed[] $rootValue - * @param mixed[] $contextValue + * @param mixed $contextValue * @param mixed[]|Traversable $rawVariableValues * @param string|null $operationName * From 0cbc1c9c0702668a759110d7aa12471819bbf456 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Fri, 8 Feb 2019 21:08:11 +0700 Subject: [PATCH 22/34] Support PHP 8 (cherry picked from commit bc637414e5158f5b2b8144be19f4baf8b4c7abc2) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 34f9d61..27a3dac 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "API" ], "require": { - "php": "^7.1", + "php": "^7.1||^8.0", "ext-json": "*", "ext-mbstring": "*" }, From 7405ddc85206b647e9ca0ea35ec543fb78d0e75b Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Mon, 11 Feb 2019 02:53:24 +0700 Subject: [PATCH 23/34] Fix incorrect array type of rootValue in PHPDocs (cherry picked from commit 77448ba6239f7a8bd413c2dfc4ef6d2c3d040009) --- src/Executor/Executor.php | 2 +- src/Executor/ReferenceExecutor.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index c87558d..55fd0fb 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -119,7 +119,7 @@ class Executor * * Useful for async PHP platforms. * - * @param mixed[]|null $rootValue + * @param mixed|null $rootValue * @param mixed|null $contextValue * @param mixed[]|null $variableValues * @param string|null $operationName diff --git a/src/Executor/ReferenceExecutor.php b/src/Executor/ReferenceExecutor.php index e61e5ea..97a5dc1 100644 --- a/src/Executor/ReferenceExecutor.php +++ b/src/Executor/ReferenceExecutor.php @@ -115,7 +115,7 @@ class ReferenceExecutor implements ExecutorImplementation * Constructs an ExecutionContext object from the arguments passed to * execute, which we will pass throughout the other execution methods. * - * @param mixed[] $rootValue + * @param mixed $rootValue * @param mixed $contextValue * @param mixed[]|Traversable $rawVariableValues * @param string|null $operationName From 8c66fa8d1ee381c19012e0e456bf4e738bb36027 Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Mon, 11 Feb 2019 02:53:36 +0700 Subject: [PATCH 24/34] Standardise whitespace (cherry picked from commit d20a6a9d56fd02639e6cf0750b1b441f98e639f8) --- src/Executor/Executor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 55fd0fb..30ccad4 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -161,9 +161,9 @@ class Executor * and returns it as the result, or if it's a function, returns the result * of calling that function while passing along args and context. * - * @param mixed $source - * @param mixed[] $args - * @param mixed|null $context + * @param mixed $source + * @param mixed[] $args + * @param mixed|null $context * * @return mixed|null */ From 255ecbd709315bb7aff857264f344b7469b31a68 Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Sun, 10 Feb 2019 02:18:27 +0700 Subject: [PATCH 25/34] Test against PHP 7.4 (cherry picked from commit dfefdf24cb482e0551147aeae5eda4fe1364105e) --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6065add..ac70819 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4snapshot - nightly env: @@ -29,6 +30,7 @@ script: ./vendor/bin/phpunit --group default,ReactPromise jobs: allow_failures: + - php: 7.4snapshot - php: nightly include: From 0226b08429e5a102ea99cda9ddc4a7185006473f Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 9 Mar 2019 22:34:29 +0700 Subject: [PATCH 26/34] v0.13.1 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac09c4..1b0eecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Changelog +#### v0.13.1 +- Better validation of field/directive arguments +- Support for apollo client/server persisted queries +- Minor tweaks and fixes + ## v0.13.0 This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details. From 9fc4d114250271c37d9786a78d84549386f2be3c Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Fri, 10 May 2019 13:03:54 +0700 Subject: [PATCH 27/34] v0.13.2 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b0eecd..8c270d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +#### v0.13.2 +- Added QueryPlan support (#436) +- Fixed an issue with NodeList iteration over missing keys (#475) + #### v0.13.1 - Better validation of field/directive arguments - Support for apollo client/server persisted queries From c803c455b4a4dc8ff3da74016a121a7cae21ae8e Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Fri, 10 May 2019 14:22:06 +0200 Subject: [PATCH 28/34] Fix bc --- src/Validator/Rules/QueryDepth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validator/Rules/QueryDepth.php b/src/Validator/Rules/QueryDepth.php index 6dd9168..c840f74 100644 --- a/src/Validator/Rules/QueryDepth.php +++ b/src/Validator/Rules/QueryDepth.php @@ -99,7 +99,7 @@ class QueryDepth extends QuerySecurityRule /** * Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0. */ - public function setMaxQueryDepth(int $maxQueryDepth) + public function setMaxQueryDepth($maxQueryDepth) { $this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth); From 27539d5af07eb32048ecef2b14ffa60324987842 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 11 May 2019 14:48:12 +0700 Subject: [PATCH 29/34] v0.13.3 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c270d2..2db2fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +#### v0.13.3 +- Reverted minor possible breaking change (#476) + #### v0.13.2 - Added QueryPlan support (#436) - Fixed an issue with NodeList iteration over missing keys (#475) From 40ac5ed269187ebc92621bf3018dc3b83541c661 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Mon, 13 May 2019 07:50:47 +0200 Subject: [PATCH 30/34] Force int when setting max query depth --- src/Validator/Rules/QueryDepth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Validator/Rules/QueryDepth.php b/src/Validator/Rules/QueryDepth.php index c840f74..e4b31a0 100644 --- a/src/Validator/Rules/QueryDepth.php +++ b/src/Validator/Rules/QueryDepth.php @@ -103,7 +103,7 @@ class QueryDepth extends QuerySecurityRule { $this->checkIfGreaterOrEqualToZero('maxQueryDepth', $maxQueryDepth); - $this->maxQueryDepth = $maxQueryDepth; + $this->maxQueryDepth = (int) $maxQueryDepth; } public static function maxQueryDepthErrorMessage($max, $count) From e55c7d72cb2d852322ebad8ce30705ae90f20717 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Mon, 13 May 2019 14:12:05 +0700 Subject: [PATCH 31/34] v0.13.4 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db2fdb..603741a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +#### v0.13.4 +- Force int when setting max query depth (#477) + #### v0.13.3 - Reverted minor possible breaking change (#476) From 0b4b1485e07ecbfb02b3d16c6106b9f2106c179e Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Sun, 2 Jun 2019 20:15:33 +0200 Subject: [PATCH 32/34] Add a test to cover promises --- tests/GraphQLTest.php | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/GraphQLTest.php diff --git a/tests/GraphQLTest.php b/tests/GraphQLTest.php new file mode 100644 index 0000000..9617e9e --- /dev/null +++ b/tests/GraphQLTest.php @@ -0,0 +1,47 @@ + new ObjectType( + [ + 'name' => 'Query', + 'fields' => [ + 'sayHi' => [ + 'type' => Type::nonNull(Type::string()), + 'args' => [ + 'name' => [ + 'type' => Type::nonNull(Type::string()), + ], + ], + 'resolve' => static function ($value, $args) use ($promiseAdapter) { + return $promiseAdapter->createFulfilled(sprintf('Hi %s!', $args['name'])); + }, + ], + ], + ] + ), + ] + ); + + $promise = GraphQL::promiseToExecute($promiseAdapter, $schema, '{ sayHi(name: "John") }'); + $result = $promiseAdapter->wait($promise); + $this->assertSame(['data' => ['sayHi' => 'Hi John!']], $result->toArray()); + } +} From 08d9493b2c53aae178c0eb84432930ee24386ee5 Mon Sep 17 00:00:00 2001 From: Jeremiah VALERIE Date: Sun, 2 Jun 2019 20:25:22 +0200 Subject: [PATCH 33/34] Fix coroutine executor when using with promise --- .../Executor/CoroutineExecutor.php | 22 +++++++++++++++---- tests/GraphQLTest.php | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Experimental/Executor/CoroutineExecutor.php b/src/Experimental/Executor/CoroutineExecutor.php index d985633..91091b4 100644 --- a/src/Experimental/Executor/CoroutineExecutor.php +++ b/src/Experimental/Executor/CoroutineExecutor.php @@ -293,13 +293,17 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation $strand->stack[$strand->depth++] = $strand->current; $strand->current = $value; goto START; - } elseif ($this->promiseAdapter->isThenable($value)) { + } elseif ($this->isPromise($value)) { // !!! increment pending before calling ->then() as it may invoke the callback right away ++$this->pending; + if (! $value instanceof Promise) { + $value = $this->promiseAdapter->convertThenable($value); + } + $this->promiseAdapter - ->convertThenable($value) ->then( + $value, function ($value) use ($strand) { $strand->success = true; $strand->value = $value; @@ -478,7 +482,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation private function completeValueFast(CoroutineContext $ctx, Type $type, $value, array $path, &$returnValue) : bool { // special handling of Throwable inherited from JS reference implementation, but makes no sense in this PHP - if ($this->promiseAdapter->isThenable($value) || $value instanceof Throwable) { + if ($this->isPromise($value) || $value instanceof Throwable) { return false; } @@ -574,7 +578,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation // !!! $value might be promise, yield to resolve try { - if ($this->promiseAdapter->isThenable($value)) { + if ($this->isPromise($value)) { $value = yield $value; } } catch (Throwable $reason) { @@ -931,4 +935,14 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation return $selectedType; } + + /** + * @param mixed $value + * + * @return bool + */ + private function isPromise($value) + { + return $value instanceof Promise || $this->promiseAdapter->isThenable($value); + } } diff --git a/tests/GraphQLTest.php b/tests/GraphQLTest.php index 9617e9e..13193b5 100644 --- a/tests/GraphQLTest.php +++ b/tests/GraphQLTest.php @@ -42,6 +42,6 @@ class GraphQLTest extends TestCase $promise = GraphQL::promiseToExecute($promiseAdapter, $schema, '{ sayHi(name: "John") }'); $result = $promiseAdapter->wait($promise); - $this->assertSame(['data' => ['sayHi' => 'Hi John!']], $result->toArray()); + self::assertSame(['data' => ['sayHi' => 'Hi John!']], $result->toArray()); } } From cdcf5b44737ee743358f5ed25b19a39ad7daf777 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Wed, 12 Jun 2019 14:16:37 +0700 Subject: [PATCH 34/34] v0.13.5 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 603741a..f501f3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +#### v0.13.5 +- Fix coroutine executor when using with promise (#486) + #### v0.13.4 - Force int when setting max query depth (#477)