From fdfcd886d1d430e6d587f002e4c0d09fb3766784 Mon Sep 17 00:00:00 2001 From: spawnia Date: Wed, 18 Sep 2019 17:40:59 +0200 Subject: [PATCH 01/28] wip --- src/Type/TypeKind.php | 16 +- src/Utils/BuildClientSchema.php | 252 ++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+), 8 deletions(-) create mode 100644 src/Utils/BuildClientSchema.php diff --git a/src/Type/TypeKind.php b/src/Type/TypeKind.php index 7cd38ff07..a6f7e0e5c 100644 --- a/src/Type/TypeKind.php +++ b/src/Type/TypeKind.php @@ -6,12 +6,12 @@ class TypeKind { - const SCALAR = 0; - const OBJECT = 1; - const INTERFACE_KIND = 2; - const UNION = 3; - const ENUM = 4; - const INPUT_OBJECT = 5; - const LIST_KIND = 6; - const NON_NULL = 7; + const SCALAR = 'SCALAR'; + const OBJECT = 'OBJECT'; + const INTERFACE_KIND = 'INTERFACE_KIND'; + const UNION = 'UNION'; + const ENUM = 'ENUM'; + const INPUT_OBJECT = 'INPUT_OBJECT'; + const LIST_KIND = 'LIST_KIND'; + const NON_NULL = 'NON_NULL'; } diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php new file mode 100644 index 000000000..c5ded1cb2 --- /dev/null +++ b/src/Utils/BuildClientSchema.php @@ -0,0 +1,252 @@ +introspection = $introspectionQuery; + $this->typeConfigDecorator = $typeConfigDecorator; + $this->options = $options; + } + + /** + * Build a GraphQLSchema for use by client tools. + * + * Given the result of a client running the introspection query, creates and + * returns a GraphQLSchema instance which can be then used with all graphql-js + * tools, but cannot be used to execute a query, as introspection does not + * represent the "resolver", "parse" or "serialize" functions or any other + * server-internal mechanisms. + * + * This function expects a complete introspection result. Don't forget to check + * the "errors" field of a server response before calling this function. + * + * Accepts options as a third argument: + * + * - assumeValid: + * When building a schema from a GraphQL service's introspection result, it + * might be safe to assume the schema is valid. Set to true to assume the + * produced schema is valid. + * + * Default: false + * + * @param bool[] $options + * + * @throws Error + * + * @api + */ + public static function build(\stdClass $introspectionQuery, ?callable $typeConfigDecorator = null, array $options = []): Schema + { + $builder = new self($introspectionQuery, $typeConfigDecorator, $options); + + return $builder->buildSchema(); + } + + public function buildSchema() + { + if(! property_exists($this->introspection, '__schema')) { + throw new InvariantViolation('Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ' . json_encode($this->introspection)); + } + + $schemaIntrospection = $this->introspection->__schema; + + Utils::keyValMap( + $schemaIntrospection->types, + static function (\stdClass $typeIntrospection) { + return $typeIntrospection->name; + }, + static function (\stdClass $typeIntrospection) { + return + } + ) + $schemaDef = null; + $typeDefs = []; + $this->nodeMap = []; + $directiveDefs = []; + foreach ($this->ast->definitions as $definition) { + switch (true) { + case $definition instanceof SchemaDefinitionNode: + $schemaDef = $definition; + break; + case $definition instanceof ScalarTypeDefinitionNode: + case $definition instanceof ObjectTypeDefinitionNode: + case $definition instanceof InterfaceTypeDefinitionNode: + case $definition instanceof EnumTypeDefinitionNode: + case $definition instanceof UnionTypeDefinitionNode: + case $definition instanceof InputObjectTypeDefinitionNode: + $typeName = $definition->name->value; + if (! empty($this->nodeMap[$typeName])) { + throw new Error(sprintf('Type "%s" was defined more than once.', $typeName)); + } + $typeDefs[] = $definition; + $this->nodeMap[$typeName] = $definition; + break; + case $definition instanceof DirectiveDefinitionNode: + $directiveDefs[] = $definition; + break; + } + } + + $operationTypes = $schemaDef !== null + ? $this->getOperationTypes($schemaDef) + : [ + 'query' => isset($this->nodeMap['Query']) ? 'Query' : null, + 'mutation' => isset($this->nodeMap['Mutation']) ? 'Mutation' : null, + 'subscription' => isset($this->nodeMap['Subscription']) ? 'Subscription' : null, + ]; + + $DefinitionBuilder = new ASTDefinitionBuilder( + $this->nodeMap, + $this->options, + static function ($typeName) { + throw new Error('Type "' . $typeName . '" not found in document.'); + }, + $this->typeConfigDecorator + ); + + $directives = array_map( + static function ($def) use ($DefinitionBuilder) { + return $DefinitionBuilder->buildDirective($def); + }, + $directiveDefs + ); + + // If specified directives were not explicitly declared, add them. + $directivesByName = Utils::groupBy( + $directives, + static function (Directive $directive) : string { + return $directive->name; + } + ); + if (! isset($directivesByName['skip'])) { + $directives[] = Directive::skipDirective(); + } + if (! isset($directivesByName['include'])) { + $directives[] = Directive::includeDirective(); + } + if (! isset($directivesByName['deprecated'])) { + $directives[] = Directive::deprecatedDirective(); + } + + // Note: While this could make early assertions to get the correctly + // typed values below, that would throw immediately while type system + // validation with validateSchema() will produce more actionable results. + + return new Schema([ + 'query' => isset($operationTypes['query']) + ? $DefinitionBuilder->buildType($operationTypes['query']) + : null, + 'mutation' => isset($operationTypes['mutation']) + ? $DefinitionBuilder->buildType($operationTypes['mutation']) + : null, + 'subscription' => isset($operationTypes['subscription']) + ? $DefinitionBuilder->buildType($operationTypes['subscription']) + : null, + 'typeLoader' => static function ($name) use ($DefinitionBuilder) { + return $DefinitionBuilder->buildType($name); + }, + 'directives' => $directives, + 'astNode' => $schemaDef, + 'types' => function () use ($DefinitionBuilder) { + $types = []; + /** @var ScalarTypeDefinitionNode|ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|UnionTypeDefinitionNode|EnumTypeDefinitionNode|InputObjectTypeDefinitionNode $def */ + foreach ($this->nodeMap as $name => $def) { + $types[] = $DefinitionBuilder->buildType($def->name->value); + } + + return $types; + }, + ]); + } + + private function buildType(\stdClass $type): NamedType + { + if(property_exists($type, 'name') && property_exists($type, 'kind')) { + switch($type->kind) { + case TypeKind::SCALAR: + return $this->buildScalarDef($type); + } + } + $opTypes = []; + + foreach ($schemaDef->operationTypes as $operationType) { + $typeName = $operationType->type->name->value; + $operation = $operationType->operation; + + if (isset($opTypes[$operation])) { + throw new Error(sprintf('Must provide only one %s type in schema.', $operation)); + } + + if (! isset($this->nodeMap[$typeName])) { + throw new Error(sprintf('Specified %s type "%s" not found in document.', $operation, $typeName)); + } + + $opTypes[$operation] = $typeName; + } + + return $opTypes; + } + + private function buildScalarDef(\stdClass $scalar): ScalarType + { + $name = $scalar->name; + + $standardTypes = Type::getStandardTypes(); + if(isset($standardTypes[$name])) { + return $standardTypes[$name]; + } + + return new CustomScalarType([ + 'name' => $name, + 'description' => $scalar->description, + ]); + } +} From 121e580d146a5177d50f2b03b407e31abf3f0ab6 Mon Sep 17 00:00:00 2001 From: spawnia Date: Wed, 18 Sep 2019 18:16:33 +0200 Subject: [PATCH 02/28] wip --- src/Utils/BuildClientSchema.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index c5ded1cb2..ceb29e102 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -104,9 +104,10 @@ static function (\stdClass $typeIntrospection) { return $typeIntrospection->name; }, static function (\stdClass $typeIntrospection) { - return + return $this->buildType($typeIntrospection); } - ) + ); + $schemaDef = null; $typeDefs = []; $this->nodeMap = []; From fccc25d478eaf370a3002fb26221ec045d46f077 Mon Sep 17 00:00:00 2001 From: spawnia Date: Thu, 19 Sep 2019 20:28:34 +0200 Subject: [PATCH 03/28] Finish implementation --- src/Type/Introspection.php | 8 +- src/Type/TypeKind.php | 16 +- src/Utils/BuildClientSchema.php | 464 ++++++++++++++++++++++---------- 3 files changed, 332 insertions(+), 156 deletions(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index a15900609..0465647cc 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -264,7 +264,7 @@ public static function _type() 'resolve' => static function (Type $type) { switch (true) { case $type instanceof ListOfType: - return TypeKind::LIST_KIND; + return TypeKind::LIST; case $type instanceof NonNull: return TypeKind::NON_NULL; case $type instanceof ScalarType: @@ -276,7 +276,7 @@ public static function _type() case $type instanceof InputObjectType: return TypeKind::INPUT_OBJECT; case $type instanceof InterfaceType: - return TypeKind::INTERFACE_KIND; + return TypeKind::INTERFACE; case $type instanceof UnionType: return TypeKind::UNION; default: @@ -409,7 +409,7 @@ public static function _typeKind() 'description' => 'Indicates this type is an object. `fields` and `interfaces` are valid fields.', ], 'INTERFACE' => [ - 'value' => TypeKind::INTERFACE_KIND, + 'value' => TypeKind::INTERFACE, 'description' => 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.', ], 'UNION' => [ @@ -425,7 +425,7 @@ public static function _typeKind() 'description' => 'Indicates this type is an input object. `inputFields` is a valid field.', ], 'LIST' => [ - 'value' => TypeKind::LIST_KIND, + 'value' => TypeKind::LIST, 'description' => 'Indicates this type is a list. `ofType` is a valid field.', ], 'NON_NULL' => [ diff --git a/src/Type/TypeKind.php b/src/Type/TypeKind.php index a6f7e0e5c..69cdf2e2c 100644 --- a/src/Type/TypeKind.php +++ b/src/Type/TypeKind.php @@ -6,12 +6,12 @@ class TypeKind { - const SCALAR = 'SCALAR'; - const OBJECT = 'OBJECT'; - const INTERFACE_KIND = 'INTERFACE_KIND'; - const UNION = 'UNION'; - const ENUM = 'ENUM'; - const INPUT_OBJECT = 'INPUT_OBJECT'; - const LIST_KIND = 'LIST_KIND'; - const NON_NULL = 'NON_NULL'; + const SCALAR = 'SCALAR'; + const OBJECT = 'OBJECT'; + const INTERFACE = 'INTERFACE'; + const UNION = 'UNION'; + const ENUM = 'ENUM'; + const INPUT_OBJECT = 'INPUT_OBJECT'; + const LIST = 'LIST'; + const NON_NULL = 'NON_NULL'; } diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index ceb29e102..7a96dc5af 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -6,54 +6,51 @@ use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; -use GraphQL\Language\AST\DirectiveDefinitionNode; -use GraphQL\Language\AST\DocumentNode; -use GraphQL\Language\AST\EnumTypeDefinitionNode; -use GraphQL\Language\AST\InputObjectTypeDefinitionNode; -use GraphQL\Language\AST\InterfaceTypeDefinitionNode; -use GraphQL\Language\AST\NodeKind; -use GraphQL\Language\AST\ObjectTypeDefinitionNode; -use GraphQL\Language\AST\ScalarTypeDefinitionNode; -use GraphQL\Language\AST\SchemaDefinitionNode; -use GraphQL\Language\AST\TypeDefinitionNode; -use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\Parser; -use GraphQL\Language\Source; use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\Directive; +use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\InputObjectType; +use GraphQL\Type\Definition\InputType; +use GraphQL\Type\Definition\InterfaceType; +use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NamedType; +use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; +use GraphQL\Type\Definition\UnionType; +use GraphQL\Type\Introspection; use GraphQL\Type\Schema; +use GraphQL\Type\SchemaConfig; use GraphQL\Type\TypeKind; -use GraphQL\Validator\DocumentValidator; +use stdClass; use function array_map; -use function array_reduce; -use function sprintf; +use function array_merge; +use function json_encode; +use function property_exists; class BuildClientSchema { - /** @var \stdClass */ + /** @var stdClass */ private $introspection; - /** @var TypeDefinitionNode[] */ - private $nodeMap; - - /** @var callable|null */ - private $typeConfigDecorator; - /** @var bool[] */ private $options; + /** @var NamedType[] */ + private $typeMap; + /** - * @paran \stdClass $introspectionQuery * @param bool[] $options + * + * @paran \stdClass $introspectionQuery */ - public function __construct(\stdClass $introspectionQuery, ?callable $typeConfigDecorator = null, array $options = []) + public function __construct(stdClass $introspectionQuery, array $options = []) { - $this->introspection = $introspectionQuery; - $this->typeConfigDecorator = $typeConfigDecorator; - $this->options = $options; + $this->introspection = $introspectionQuery; + $this->options = $options; } /** @@ -83,171 +80,350 @@ public function __construct(\stdClass $introspectionQuery, ?callable $typeConfig * * @api */ - public static function build(\stdClass $introspectionQuery, ?callable $typeConfigDecorator = null, array $options = []): Schema + public static function build(stdClass $introspectionQuery, array $options = []) : Schema { - $builder = new self($introspectionQuery, $typeConfigDecorator, $options); + $builder = new self($introspectionQuery, $options); return $builder->buildSchema(); } public function buildSchema() { - if(! property_exists($this->introspection, '__schema')) { + if (! property_exists($this->introspection, '__schema')) { throw new InvariantViolation('Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ' . json_encode($this->introspection)); } $schemaIntrospection = $this->introspection->__schema; - Utils::keyValMap( + $this->typeMap = Utils::keyValMap( $schemaIntrospection->types, - static function (\stdClass $typeIntrospection) { + static function (stdClass $typeIntrospection) { return $typeIntrospection->name; }, - static function (\stdClass $typeIntrospection) { + function (stdClass $typeIntrospection) { return $this->buildType($typeIntrospection); } ); - $schemaDef = null; - $typeDefs = []; - $this->nodeMap = []; - $directiveDefs = []; - foreach ($this->ast->definitions as $definition) { - switch (true) { - case $definition instanceof SchemaDefinitionNode: - $schemaDef = $definition; - break; - case $definition instanceof ScalarTypeDefinitionNode: - case $definition instanceof ObjectTypeDefinitionNode: - case $definition instanceof InterfaceTypeDefinitionNode: - case $definition instanceof EnumTypeDefinitionNode: - case $definition instanceof UnionTypeDefinitionNode: - case $definition instanceof InputObjectTypeDefinitionNode: - $typeName = $definition->name->value; - if (! empty($this->nodeMap[$typeName])) { - throw new Error(sprintf('Type "%s" was defined more than once.', $typeName)); - } - $typeDefs[] = $definition; - $this->nodeMap[$typeName] = $definition; - break; - case $definition instanceof DirectiveDefinitionNode: - $directiveDefs[] = $definition; - break; + $builtInTypes = array_merge( + Type::getStandardTypes(), + Introspection::getTypes() + ); + foreach ($builtInTypes as $name => $type) { + if (! isset($this->typeMap[$name])) { + continue; } + + $this->typeMap[$name] = $type; } - $operationTypes = $schemaDef !== null - ? $this->getOperationTypes($schemaDef) - : [ - 'query' => isset($this->nodeMap['Query']) ? 'Query' : null, - 'mutation' => isset($this->nodeMap['Mutation']) ? 'Mutation' : null, - 'subscription' => isset($this->nodeMap['Subscription']) ? 'Subscription' : null, - ]; + $queryType = $schemaIntrospection->queryType + ? $this->getObjectType($schemaIntrospection->queryType) + : null; - $DefinitionBuilder = new ASTDefinitionBuilder( - $this->nodeMap, - $this->options, - static function ($typeName) { - throw new Error('Type "' . $typeName . '" not found in document.'); - }, - $this->typeConfigDecorator - ); + $mutationType = $schemaIntrospection->mutationType + ? $this->getObjectType($schemaIntrospection->mutationType) + : null; - $directives = array_map( - static function ($def) use ($DefinitionBuilder) { - return $DefinitionBuilder->buildDirective($def); - }, - $directiveDefs - ); + $subscriptionType = $schemaIntrospection->subscriptionType + ? $this->getObjectType($schemaIntrospection->subscriptionType) + : null; + + $directives = $schemaIntrospection->directives + ? array_map( + [$this, 'buildDirective'], + $schemaIntrospection->directives + ) + : []; + + $schemaConfig = new SchemaConfig(); + $schemaConfig->setQuery($queryType) + ->setMutation($mutationType) + ->setSubscription($subscriptionType) + ->setTypes($this->typeMap) + ->setDirectives($directives) + ->setAssumeValid( + isset($this->options) + && isset($this->options['assumeValid']) + && $this->options['assumeValid'] + ); + + return new Schema($schemaConfig); + } + + private function getType(stdClass $typeRef) : Type + { + if ($typeRef->kind === TypeKind::LIST) { + $itemRef = $typeRef->ofType; + if (! $itemRef) { + throw new InvariantViolation('Decorated type deeper than introspection query.'); + } - // If specified directives were not explicitly declared, add them. - $directivesByName = Utils::groupBy( - $directives, - static function (Directive $directive) : string { - return $directive->name; + return new ListOfType($this->getType($itemRef)); + } + + if ($typeRef->kind === TypeKind::NON_NULL) { + $nullableRef = $typeRef->ofType; + if (! $nullableRef) { + throw new InvariantViolation('Decorated type deeper than introspection query.'); } + $nullableType = $this->getType($nullableRef); + + return new NonNull( + NonNull::assertNullableType($nullableType) + ); + } + + if (! $typeRef->name) { + throw new InvariantViolation('Unknown type reference: ' . json_encode($typeRef)); + } + + return $this->getNamedType($typeRef->name); + } + + private function getNamedType(string $typeName) : NamedType + { + if (! isset($this->typeMap[$typeName])) { + throw new InvariantViolation( + "Invalid or incomplete schema, unknown type: ${typeName}. Ensure that a full introspection query is used in order to build a client schema." + ); + } + + return $this->typeMap[$typeName]; + } + + private function getInputType(stdClass $typeRef) : InputType + { + $type = $this->getType($typeRef); + + if ($type instanceof InputType) { + return $type; + } + + throw new InvariantViolation('Introspection must provide input type for arguments, but received: ' . json_encode($type)); + } + + private function getOutputType(stdClass $typeRef) : OutputType + { + $type = $this->getType($typeRef); + + if ($type instanceof OutputType) { + return $type; + } + + throw new InvariantViolation('Introspection must provide output type for fields, but received: ' . json_encode($type)); + } + + private function getObjectType(stdClass $typeRef) : ObjectType + { + $type = $this->getType($typeRef); + + return ObjectType::assertObjectType($type); + } + + private function getInterfaceType(stdClass $typeRef) : InterfaceType + { + $type = $this->getType($typeRef); + + return InterfaceType::assertInterfaceType($type); + } + + private function buildType(stdClass $type) : NamedType + { + if (property_exists($type, 'name') && property_exists($type, 'kind')) { + switch ($type->kind) { + case TypeKind::SCALAR: + return $this->buildScalarDef($type); + case TypeKind::OBJECT: + return $this->buildObjectDef($type); + case TypeKind::INTERFACE: + return $this->buildInterfaceDef($type); + case TypeKind::UNION: + return $this->buildUnionDef($type); + case TypeKind::ENUM: + return $this->buildEnumDef($type); + case TypeKind::INPUT_OBJECT: + return $this->buildInputObjectDef($type); + } + } + + throw new InvariantViolation( + 'Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: ' . json_encode($type) ); - if (! isset($directivesByName['skip'])) { - $directives[] = Directive::skipDirective(); - } - if (! isset($directivesByName['include'])) { - $directives[] = Directive::includeDirective(); - } - if (! isset($directivesByName['deprecated'])) { - $directives[] = Directive::deprecatedDirective(); - } - - // Note: While this could make early assertions to get the correctly - // typed values below, that would throw immediately while type system - // validation with validateSchema() will produce more actionable results. - - return new Schema([ - 'query' => isset($operationTypes['query']) - ? $DefinitionBuilder->buildType($operationTypes['query']) - : null, - 'mutation' => isset($operationTypes['mutation']) - ? $DefinitionBuilder->buildType($operationTypes['mutation']) - : null, - 'subscription' => isset($operationTypes['subscription']) - ? $DefinitionBuilder->buildType($operationTypes['subscription']) - : null, - 'typeLoader' => static function ($name) use ($DefinitionBuilder) { - return $DefinitionBuilder->buildType($name); + } + + private function buildScalarDef(stdClass $scalar) : ScalarType + { + return new CustomScalarType([ + 'name' => $scalar->name, + 'description' => $scalar->description, + ]); + } + + private function buildObjectDef(stdClass $object) : ObjectType + { + if (! property_exists($object, 'interfaces')) { + throw new InvariantViolation('Introspection result missing interfaces: ' . json_encode($object)); + } + + return new ObjectType([ + 'name' => $object->name, + 'description' => $object->description, + 'interfaces' => function () use ($object) { + return array_map( + [$this, 'getInterfaceType'], + $object->interfaces + ); + }, + 'fields' => function () use ($object) { + return $this->buildFieldDefMap($object); }, - 'directives' => $directives, - 'astNode' => $schemaDef, - 'types' => function () use ($DefinitionBuilder) { - $types = []; - /** @var ScalarTypeDefinitionNode|ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|UnionTypeDefinitionNode|EnumTypeDefinitionNode|InputObjectTypeDefinitionNode $def */ - foreach ($this->nodeMap as $name => $def) { - $types[] = $DefinitionBuilder->buildType($def->name->value); + ]); + } + + private function buildInterfaceDef(stdClass $interface) : InterfaceType + { + return new InterfaceType([ + 'name' => $interface->name, + 'description' => $interface->description, + 'fields' => function () use ($interface) { + return $this->buildFieldDefMap($interface); + }, + ]); + } + + private function buildUnionDef(stdClass $union) : UnionType + { + if (! property_exists($union, 'possibleType')) { + throw new InvariantViolation('Introspection result missing possibleTypes: ' . json_encode($union)); + } + + return new UnionType([ + 'name' => $union->name, + 'description' => $union->description, + 'types' => function () use ($union) { + return array_map( + [$this, 'getObjectType'], + $union->possibleTypes + ); + }, + ]); + } + + private function buildEnumDef(stdClass $enum) : EnumType + { + if (! property_exists($enum, 'enumValues')) { + throw new InvariantViolation('Introspection result missing enumValues: ' . json_encode($enum)); + } + + return new EnumType([ + 'name' => $enum->name, + 'description' => $enum->description, + 'values' => Utils::keyValMap( + $enum->enumValues, + static function (stdClass $enumValue) : string { + return $enumValue->name; + }, + static function (stdClass $enumValue) { + return [ + 'description' => $enumValue->description, + 'deprecationReason' => $enumValue->deprecationReason, + ]; } + ), + ]); + } + + private function buildInputObjectDef(stdClass $inputObject) : InputObjectType + { + if (! property_exists($inputObject, 'inputFields')) { + throw new InvariantViolation('Introspection result missing inputFields: ' . json_encode($inputObject)); + } - return $types; + return new InputObjectType([ + 'name' => $inputObject->name, + 'description' => $inputObject->description, + 'fields' => function () use ($inputObject) { + return $this->buildInputValueDefMap($inputObject->inputFields); }, ]); } - private function buildType(\stdClass $type): NamedType + private function buildFieldDefMap(stdClass $typeIntrospection) { - if(property_exists($type, 'name') && property_exists($type, 'kind')) { - switch($type->kind) { - case TypeKind::SCALAR: - return $this->buildScalarDef($type); - } + if (! property_exists($typeIntrospection, 'fields')) { + throw new InvariantViolation('Introspection result missing fields: ' . json_encode($typeIntrospection)); } - $opTypes = []; - foreach ($schemaDef->operationTypes as $operationType) { - $typeName = $operationType->type->name->value; - $operation = $operationType->operation; + return Utils::keyValMap( + $typeIntrospection->fields, + static function (stdClass $fieldIntrospection) : string { + return $fieldIntrospection->name; + }, + function (stdClass $fieldIntrospection) { + if (! property_exists($fieldIntrospection, 'args')) { + throw new InvariantViolation('Introspection result missing field args: ' . json_encode($fieldIntrospection)); + } - if (isset($opTypes[$operation])) { - throw new Error(sprintf('Must provide only one %s type in schema.', $operation)); + return [ + 'description' => $fieldIntrospection->description, + 'deprecationReason' => $fieldIntrospection->deprecationReason, + 'type' => $this->getOutputType($fieldIntrospection->type), + 'args' => $this->buildInputValueDefMap($fieldIntrospection->args), + ]; } + ); + } - if (! isset($this->nodeMap[$typeName])) { - throw new Error(sprintf('Specified %s type "%s" not found in document.', $operation, $typeName)); - } + /** + * @param stdClass[] $inputValueIntrospections + * + * @return mixed[][] + */ + private function buildInputValueDefMap(array $inputValueIntrospections) + { + return Utils::keyValMap( + $inputValueIntrospections, + static function (stdClass $inputValue) : string { + return $inputValue->name; + }, + [$this, 'buildInputValue'] + ); + } + + private function buildInputValue(stdClass $inputValueIntrospection) + { + $type = $this->getInputType($inputValueIntrospection->type); + + $inputValue = [ + 'description' => $inputValueIntrospection->description, + 'type' => $type, + ]; - $opTypes[$operation] = $typeName; + if (! $inputValueIntrospection->defaultValue) { + return; } - return $opTypes; + $inputValue['defaultValue'] = AST::valueFromAST( + Parser::parseValue($inputValueIntrospection->defaultValue), + $type + ); } - private function buildScalarDef(\stdClass $scalar): ScalarType + private function buildDirective(stdClass $directive) : Directive { - $name = $scalar->name; - - $standardTypes = Type::getStandardTypes(); - if(isset($standardTypes[$name])) { - return $standardTypes[$name]; + if (! property_exists($directive, 'args')) { + throw new InvariantViolation('Introspection result missing directive args: ' . json_encode($directive)); + } + if (! property_exists($directive, 'locations')) { + throw new InvariantViolation('Introspection result missing directive locations: ' . json_encode($directive)); } - return new CustomScalarType([ - 'name' => $name, - 'description' => $scalar->description, + return new Directive([ + 'name' => $directive->name, + 'description' => $directive->description, + 'locations' => $directive->locations, + 'args' => $this->buildInputValueDefMap($directive->args), ]); } } From 6c62ecda9b983efb93c0cc42561398ac50b475b3 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sat, 21 Sep 2019 19:16:37 +0200 Subject: [PATCH 04/28] Add Introspection::fromSchema --- src/Type/Introspection.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 0465647cc..5147a57f5 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -5,6 +5,7 @@ namespace GraphQL\Type; use Exception; +use GraphQL\GraphQL; use GraphQL\Language\DirectiveLocation; use GraphQL\Language\Printer; use GraphQL\Type\Definition\Directive; @@ -185,6 +186,32 @@ public static function getTypes() ]; } + /** + * + * Build an IntrospectionQuery from a GraphQLSchema + * + * IntrospectionQuery is useful for utilities that care about type and field + * relationships, but do not need to traverse through those relationships. + * + * This is the inverse of BuildClientSchema. The primary use case is outside + * of the server context, for instance when doing schema comparisons. + * + * Options: + * - descriptions + * Whether to include descriptions in the introspection result. + * Default: true + * + * @param bool[]|bool $options + */ + public static function fromSchema(Schema $schema, array $options = []) + { + $result = GraphQL::executeQuery( + $schema, + self::getIntrospectionQuery($options) + ); + return $result->data; + } + public static function _schema() { if (! isset(self::$map['__Schema'])) { From c4021fdde05dfa0fc83473fa1f55104ee6c045f9 Mon Sep 17 00:00:00 2001 From: spawnia Date: Sat, 21 Sep 2019 19:16:48 +0200 Subject: [PATCH 05/28] wip test --- tests/Utils/BuildClientSchemaTest.php | 64 +++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/Utils/BuildClientSchemaTest.php diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php new file mode 100644 index 000000000..b96c71fd6 --- /dev/null +++ b/tests/Utils/BuildClientSchemaTest.php @@ -0,0 +1,64 @@ + { + + /** + * @see it('builds a simple schema', () => { + */ + public function testBuildsASimpleSchema() : void + { + self::assertCycleIntrospection(' + schema { + query: Simple + } + + """This is simple type""" + type Simple { + """This is a string field""" + string: String + } + '); + } +} From 3c729c4d232f03ad65afdce3a28e6b2401538edd Mon Sep 17 00:00:00 2001 From: spawnia Date: Sat, 21 Sep 2019 19:17:08 +0200 Subject: [PATCH 06/28] format --- src/Type/Introspection.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 5147a57f5..e74919f7e 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -209,6 +209,7 @@ public static function fromSchema(Schema $schema, array $options = []) $schema, self::getIntrospectionQuery($options) ); + return $result->data; } From f8e9e6ba0bf2eee18ba92b54059dd7cb05bb3c9f Mon Sep 17 00:00:00 2001 From: spawnia Date: Sat, 21 Sep 2019 19:17:41 +0200 Subject: [PATCH 07/28] types --- src/Type/Introspection.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index e74919f7e..607660723 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -202,8 +202,9 @@ public static function getTypes() * Default: true * * @param bool[]|bool $options + * @return mixed[]|null */ - public static function fromSchema(Schema $schema, array $options = []) + public static function fromSchema(Schema $schema, array $options = []): ?array { $result = GraphQL::executeQuery( $schema, From 0f7d970fb463598aa718dab76895c640a47a42ca Mon Sep 17 00:00:00 2001 From: spawnia Date: Thu, 31 Oct 2019 23:29:34 +0100 Subject: [PATCH 08/28] Use array instead of stdClass --- src/Utils/BuildClientSchema.php | 211 ++++++++++++++++---------------- 1 file changed, 105 insertions(+), 106 deletions(-) diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 7a96dc5af..be31213b6 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -25,15 +25,13 @@ use GraphQL\Type\Schema; use GraphQL\Type\SchemaConfig; use GraphQL\Type\TypeKind; -use stdClass; use function array_map; use function array_merge; use function json_encode; -use function property_exists; class BuildClientSchema { - /** @var stdClass */ + /** @var mixed[] */ private $introspection; /** @var bool[] */ @@ -43,11 +41,10 @@ class BuildClientSchema private $typeMap; /** + * @paran mixed[] $introspectionQuery * @param bool[] $options - * - * @paran \stdClass $introspectionQuery */ - public function __construct(stdClass $introspectionQuery, array $options = []) + public function __construct(array $introspectionQuery, array $options = []) { $this->introspection = $introspectionQuery; $this->options = $options; @@ -80,7 +77,7 @@ public function __construct(stdClass $introspectionQuery, array $options = []) * * @api */ - public static function build(stdClass $introspectionQuery, array $options = []) : Schema + public static function build(array $introspectionQuery, array $options = []) : Schema { $builder = new self($introspectionQuery, $options); @@ -89,18 +86,18 @@ public static function build(stdClass $introspectionQuery, array $options = []) public function buildSchema() { - if (! property_exists($this->introspection, '__schema')) { + if (! array_key_exists('__schema', $this->introspection)) { throw new InvariantViolation('Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ' . json_encode($this->introspection)); } - $schemaIntrospection = $this->introspection->__schema; + $schemaIntrospection = $this->introspection['__schema']; $this->typeMap = Utils::keyValMap( - $schemaIntrospection->types, - static function (stdClass $typeIntrospection) { - return $typeIntrospection->name; + $schemaIntrospection['types'], + static function (array $typeIntrospection) { + return $typeIntrospection['name']; }, - function (stdClass $typeIntrospection) { + function (array $typeIntrospection) { return $this->buildType($typeIntrospection); } ); @@ -117,22 +114,22 @@ function (stdClass $typeIntrospection) { $this->typeMap[$name] = $type; } - $queryType = $schemaIntrospection->queryType - ? $this->getObjectType($schemaIntrospection->queryType) + $queryType = isset($schemaIntrospection['queryType']) + ? $this->getObjectType($schemaIntrospection['queryType']) : null; - $mutationType = $schemaIntrospection->mutationType - ? $this->getObjectType($schemaIntrospection->mutationType) + $mutationType = isset($schemaIntrospection['mutationType']) + ? $this->getObjectType($schemaIntrospection['mutationType']) : null; - $subscriptionType = $schemaIntrospection->subscriptionType - ? $this->getObjectType($schemaIntrospection->subscriptionType) + $subscriptionType = isset($schemaIntrospection['subscriptionType']) + ? $this->getObjectType($schemaIntrospection['subscriptionType']) : null; - $directives = $schemaIntrospection->directives + $directives = isset($schemaIntrospection['directives']) ? array_map( [$this, 'buildDirective'], - $schemaIntrospection->directives + $schemaIntrospection['directives'] ) : []; @@ -151,34 +148,36 @@ function (stdClass $typeIntrospection) { return new Schema($schemaConfig); } - private function getType(stdClass $typeRef) : Type + private function getType(array $typeRef) : Type { - if ($typeRef->kind === TypeKind::LIST) { - $itemRef = $typeRef->ofType; - if (! $itemRef) { - throw new InvariantViolation('Decorated type deeper than introspection query.'); + if (isset($typeRef['kind'])) { + if ($typeRef['kind'] === TypeKind::LIST) { + $itemRef = $typeRef['ofType']; + if (! $itemRef) { + throw new InvariantViolation('Decorated type deeper than introspection query.'); + } + + return new ListOfType($this->getType($itemRef)); } - return new ListOfType($this->getType($itemRef)); - } + if ($typeRef['kind'] === TypeKind::NON_NULL) { + $nullableRef = $typeRef['ofType']; + if (! $nullableRef) { + throw new InvariantViolation('Decorated type deeper than introspection query.'); + } + $nullableType = $this->getType($nullableRef); - if ($typeRef->kind === TypeKind::NON_NULL) { - $nullableRef = $typeRef->ofType; - if (! $nullableRef) { - throw new InvariantViolation('Decorated type deeper than introspection query.'); + return new NonNull( + NonNull::assertNullableType($nullableType) + ); } - $nullableType = $this->getType($nullableRef); - - return new NonNull( - NonNull::assertNullableType($nullableType) - ); } - if (! $typeRef->name) { + if (! $typeRef['name']) { throw new InvariantViolation('Unknown type reference: ' . json_encode($typeRef)); } - return $this->getNamedType($typeRef->name); + return $this->getNamedType($typeRef['name']); } private function getNamedType(string $typeName) : NamedType @@ -192,7 +191,7 @@ private function getNamedType(string $typeName) : NamedType return $this->typeMap[$typeName]; } - private function getInputType(stdClass $typeRef) : InputType + private function getInputType(array $typeRef) : InputType { $type = $this->getType($typeRef); @@ -203,7 +202,7 @@ private function getInputType(stdClass $typeRef) : InputType throw new InvariantViolation('Introspection must provide input type for arguments, but received: ' . json_encode($type)); } - private function getOutputType(stdClass $typeRef) : OutputType + private function getOutputType(array $typeRef) : OutputType { $type = $this->getType($typeRef); @@ -214,24 +213,24 @@ private function getOutputType(stdClass $typeRef) : OutputType throw new InvariantViolation('Introspection must provide output type for fields, but received: ' . json_encode($type)); } - private function getObjectType(stdClass $typeRef) : ObjectType + private function getObjectType(array $typeRef) : ObjectType { $type = $this->getType($typeRef); return ObjectType::assertObjectType($type); } - private function getInterfaceType(stdClass $typeRef) : InterfaceType + private function getInterfaceType(array $typeRef) : InterfaceType { $type = $this->getType($typeRef); return InterfaceType::assertInterfaceType($type); } - private function buildType(stdClass $type) : NamedType + private function buildType(array $type) : NamedType { - if (property_exists($type, 'name') && property_exists($type, 'kind')) { - switch ($type->kind) { + if (array_key_exists('name', $type) && array_key_exists('kind', $type)) { + switch ($type['kind']) { case TypeKind::SCALAR: return $this->buildScalarDef($type); case TypeKind::OBJECT: @@ -252,27 +251,27 @@ private function buildType(stdClass $type) : NamedType ); } - private function buildScalarDef(stdClass $scalar) : ScalarType + private function buildScalarDef(array $scalar) : ScalarType { return new CustomScalarType([ - 'name' => $scalar->name, - 'description' => $scalar->description, + 'name' => $scalar['name'], + 'description' => $scalar['description'], ]); } - private function buildObjectDef(stdClass $object) : ObjectType + private function buildObjectDef(array $object) : ObjectType { - if (! property_exists($object, 'interfaces')) { + if (! array_key_exists('interfaces', $object)) { throw new InvariantViolation('Introspection result missing interfaces: ' . json_encode($object)); } return new ObjectType([ - 'name' => $object->name, - 'description' => $object->description, + 'name' => $object['name'], + 'description' => $object['description'], 'interfaces' => function () use ($object) { return array_map( [$this, 'getInterfaceType'], - $object->interfaces + $object['interfaces'] ); }, 'fields' => function () use ($object) { @@ -281,102 +280,102 @@ private function buildObjectDef(stdClass $object) : ObjectType ]); } - private function buildInterfaceDef(stdClass $interface) : InterfaceType + private function buildInterfaceDef(array $interface) : InterfaceType { return new InterfaceType([ - 'name' => $interface->name, - 'description' => $interface->description, + 'name' => $interface['name'], + 'description' => $interface['description'], 'fields' => function () use ($interface) { return $this->buildFieldDefMap($interface); }, ]); } - private function buildUnionDef(stdClass $union) : UnionType + private function buildUnionDef(array $union) : UnionType { - if (! property_exists($union, 'possibleType')) { + if (! array_key_exists('possibleType', $union)) { throw new InvariantViolation('Introspection result missing possibleTypes: ' . json_encode($union)); } return new UnionType([ - 'name' => $union->name, - 'description' => $union->description, + 'name' => $union['name'], + 'description' => $union['description'], 'types' => function () use ($union) { return array_map( [$this, 'getObjectType'], - $union->possibleTypes + $union['possibleTypes'] ); }, ]); } - private function buildEnumDef(stdClass $enum) : EnumType + private function buildEnumDef(array $enum) : EnumType { - if (! property_exists($enum, 'enumValues')) { + if (! array_key_exists('enumValues', $enum)) { throw new InvariantViolation('Introspection result missing enumValues: ' . json_encode($enum)); } return new EnumType([ - 'name' => $enum->name, - 'description' => $enum->description, + 'name' => $enum['name'], + 'description' => $enum['description'], 'values' => Utils::keyValMap( - $enum->enumValues, - static function (stdClass $enumValue) : string { - return $enumValue->name; + $enum['enumValues'], + static function (array $enumValue) : string { + return $enumValue['name']; }, - static function (stdClass $enumValue) { + static function (array $enumValue) { return [ - 'description' => $enumValue->description, - 'deprecationReason' => $enumValue->deprecationReason, + 'description' => $enumValue['description'], + 'deprecationReason' => $enumValue['deprecationReason'], ]; } ), ]); } - private function buildInputObjectDef(stdClass $inputObject) : InputObjectType + private function buildInputObjectDef(array $inputObject) : InputObjectType { - if (! property_exists($inputObject, 'inputFields')) { + if (! array_key_exists('inputFields', $inputObject)) { throw new InvariantViolation('Introspection result missing inputFields: ' . json_encode($inputObject)); } return new InputObjectType([ - 'name' => $inputObject->name, - 'description' => $inputObject->description, + 'name' => $inputObject['name'], + 'description' => $inputObject['description'], 'fields' => function () use ($inputObject) { return $this->buildInputValueDefMap($inputObject->inputFields); }, ]); } - private function buildFieldDefMap(stdClass $typeIntrospection) + private function buildFieldDefMap(array $typeIntrospection) { - if (! property_exists($typeIntrospection, 'fields')) { + if (! array_key_exists('fields', $typeIntrospection)) { throw new InvariantViolation('Introspection result missing fields: ' . json_encode($typeIntrospection)); } return Utils::keyValMap( - $typeIntrospection->fields, - static function (stdClass $fieldIntrospection) : string { - return $fieldIntrospection->name; + $typeIntrospection['fields'], + static function (array $fieldIntrospection) : string { + return $fieldIntrospection['name']; }, - function (stdClass $fieldIntrospection) { - if (! property_exists($fieldIntrospection, 'args')) { + function (array $fieldIntrospection) { + if (! array_key_exists('args', $fieldIntrospection)) { throw new InvariantViolation('Introspection result missing field args: ' . json_encode($fieldIntrospection)); } return [ - 'description' => $fieldIntrospection->description, - 'deprecationReason' => $fieldIntrospection->deprecationReason, - 'type' => $this->getOutputType($fieldIntrospection->type), - 'args' => $this->buildInputValueDefMap($fieldIntrospection->args), + 'description' => $fieldIntrospection['description'], + 'deprecationReason' => $fieldIntrospection['deprecationReason'], + 'type' => $this->getOutputType($fieldIntrospection['type']), + 'args' => $this->buildInputValueDefMap($fieldIntrospection['args']), ]; } ); } /** - * @param stdClass[] $inputValueIntrospections + * @param array[] $inputValueIntrospections * * @return mixed[][] */ @@ -384,46 +383,46 @@ private function buildInputValueDefMap(array $inputValueIntrospections) { return Utils::keyValMap( $inputValueIntrospections, - static function (stdClass $inputValue) : string { - return $inputValue->name; + static function (array $inputValue) : string { + return $inputValue['name']; }, [$this, 'buildInputValue'] ); } - private function buildInputValue(stdClass $inputValueIntrospection) + public function buildInputValue(array $inputValueIntrospection) { - $type = $this->getInputType($inputValueIntrospection->type); + $type = $this->getInputType($inputValueIntrospection['type']); $inputValue = [ - 'description' => $inputValueIntrospection->description, + 'description' => $inputValueIntrospection['description'], 'type' => $type, ]; - if (! $inputValueIntrospection->defaultValue) { - return; + if (isset($inputValueIntrospection['defaultValue'])) { + $inputValue['defaultValue'] = AST::valueFromAST( + Parser::parseValue($inputValueIntrospection['defaultValue']), + $type + ); } - $inputValue['defaultValue'] = AST::valueFromAST( - Parser::parseValue($inputValueIntrospection->defaultValue), - $type - ); + return $inputValue; } - private function buildDirective(stdClass $directive) : Directive + public function buildDirective(array $directive) : Directive { - if (! property_exists($directive, 'args')) { + if (! array_key_exists('args', $directive)) { throw new InvariantViolation('Introspection result missing directive args: ' . json_encode($directive)); } - if (! property_exists($directive, 'locations')) { + if (! array_key_exists('locations', $directive)) { throw new InvariantViolation('Introspection result missing directive locations: ' . json_encode($directive)); } return new Directive([ - 'name' => $directive->name, - 'description' => $directive->description, - 'locations' => $directive->locations, - 'args' => $this->buildInputValueDefMap($directive->args), + 'name' => $directive['name'], + 'description' => $directive['description'], + 'locations' => $directive['locations'], + 'args' => $this->buildInputValueDefMap($directive['args']), ]); } } From 38ea6dfdeff45a2bb35864f8ec4c15a0a07ab741 Mon Sep 17 00:00:00 2001 From: spawnia Date: Thu, 31 Oct 2019 23:29:44 +0100 Subject: [PATCH 09/28] Add tests --- tests/Utils/BuildClientSchemaTest.php | 121 +++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index b96c71fd6..ba69daa53 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -18,8 +18,10 @@ use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ScalarType; +use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Introspection; +use GraphQL\Type\Schema; use GraphQL\Utils\BuildClientSchema; use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaPrinter; @@ -42,6 +44,20 @@ protected static function assertCycleIntrospection(string $sdl): void self::assertSame($initialIntrospection, $secondIntrospection); } + protected static function introspectionFromSDL(string $sdl): array + { + $schema = BuildSchema::build($sdl); + + return Introspection::fromSchema($schema); + } + + protected static function clientSchemaFromSDL(string $sdl): Schema + { + $introspection = self::introspectionFromSDL($sdl); + + return BuildClientSchema::build($introspection); + } + // describe('Type System: build schema from introspection', () => { /** @@ -54,11 +70,114 @@ public function testBuildsASimpleSchema() : void query: Simple } - """This is simple type""" + """This is a simple type""" type Simple { """This is a string field""" string: String } '); } + + /** + * it('builds a schema without the query type', () => { + */ + public function testBuildsASchemaWithoutTheQueryType(): void + { + $sdl = ' + type Query { + foo: String + } + '; + $introspection = self::introspectionFromSDL($sdl); + unset($introspection['__schema']['queryType']); + + $clientSchema = BuildClientSchema::build($introspection); + $this->assertNull($clientSchema->getQueryType()); + $this->markTestSkipped('Why should this assertion be true?'); + $this->assertSame($sdl, SchemaPrinter::printIntrospectionSchema($clientSchema)); + } + + /** + * it('builds a simple schema with all operation types', () => { + */ + public function testBuildsASimpleSchemaWithAllOperationTypes(): void + { + self::assertCycleIntrospection(' + schema { + query: QueryType + mutation: MutationType + subscription: SubscriptionType + } + + """This is a simple mutation type""" + type MutationType { + """Set the string field""" + string: String + } + + """This is a simple query type""" + type QueryType { + """This is a string field""" + string: String + } + + """This is a simple subscription type""" + type SubscriptionType { + """This is a string field""" + string: String + } + '); + } + + /** + * it('uses built-in scalars when possible', () => { + */ + public function testUsesBuiltInScalarsWhenPossible(): void + { + $sdl = ' + scalar CustomScalar + + type Query { + int: Int + float: Float + string: String + boolean: Boolean + id: ID + custom: CustomScalar + } + '; + + self::assertCycleIntrospection($sdl); + + $schema = BuildSchema::build($sdl); + $introspection = Introspection::fromSchema($schema); + $clientSchema = BuildClientSchema::build($introspection); + + // Built-ins are used + $this->assertSame(Type::int(), $clientSchema->getType('Int')); + $this->assertSame(Type::float(), $clientSchema->getType('Float')); + $this->assertSame(Type::string(), $clientSchema->getType('String')); + $this->assertSame(Type::boolean(), $clientSchema->getType('Boolean')); + $this->assertSame(Type::id(), $clientSchema->getType('ID')); + + // Custom are built + $this->assertNotSame( + $schema->getType('CustomScalar'), + $clientSchema->getType('CustomScalar') + ); + } + + /** + * it('includes standard types only if they are used', () => { + */ + public function testIncludesStandardTypesOnlyIfTheyAreUsed(): void + { + $clientSchema = self::clientSchemaFromSDL(' + type Query { + foo: String + } + '); + + $this->assertNull($clientSchema->getType('Int')); + } } From ed3e57c65c82091caa47fc96754eea147cac1300 Mon Sep 17 00:00:00 2001 From: spawnia Date: Fri, 1 Nov 2019 13:25:33 +0100 Subject: [PATCH 10/28] Mention TypeKind constants change in UPGRADE.md --- UPGRADE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UPGRADE.md b/UPGRADE.md index 99d21509f..ce5c93ea9 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -3,6 +3,10 @@ ### Breaking (major): dropped deprecations - dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`. +### Breaking: change TypeKind constants +The constants in `\GraphQL\Type\TypeKind` were partly renamed and their values +have been changed to match their name instead of a numeric index. + ## Upgrade v0.12.x > v0.13.x ### Breaking (major): minimum supported version of PHP From 10be064744c2f2daa4778fcba159fa4b4aebbef1 Mon Sep 17 00:00:00 2001 From: spawnia Date: Fri, 1 Nov 2019 13:27:08 +0100 Subject: [PATCH 11/28] Adress review comments --- src/Utils/BuildClientSchema.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index be31213b6..669c04a49 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -51,10 +51,10 @@ public function __construct(array $introspectionQuery, array $options = []) } /** - * Build a GraphQLSchema for use by client tools. + * Build a schema for use by client tools. * * Given the result of a client running the introspection query, creates and - * returns a GraphQLSchema instance which can be then used with all graphql-js + * returns a \GraphQL\Type\Schema instance which can be then used with all graphql-php * tools, but cannot be used to execute a query, as introspection does not * represent the "resolver", "parse" or "serialize" functions or any other * server-internal mechanisms. From e9dadce24b8ba8132a4c9161f3f7d64faf30e673 Mon Sep 17 00:00:00 2001 From: spawnia Date: Fri, 1 Nov 2019 13:35:06 +0100 Subject: [PATCH 12/28] Some codestyle fixes --- src/Type/Introspection.php | 4 ++-- src/Utils/BuildClientSchema.php | 13 ++++++++----- tests/Utils/BuildClientSchemaTest.php | 26 +++++++++++++------------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 607660723..076069410 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -187,7 +187,6 @@ public static function getTypes() } /** - * * Build an IntrospectionQuery from a GraphQLSchema * * IntrospectionQuery is useful for utilities that care about type and field @@ -202,9 +201,10 @@ public static function getTypes() * Default: true * * @param bool[]|bool $options + * * @return mixed[]|null */ - public static function fromSchema(Schema $schema, array $options = []): ?array + public static function fromSchema(Schema $schema, array $options = []) : ?array { $result = GraphQL::executeQuery( $schema, diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 669c04a49..aa54accca 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -25,6 +25,7 @@ use GraphQL\Type\Schema; use GraphQL\Type\SchemaConfig; use GraphQL\Type\TypeKind; +use function array_key_exists; use function array_map; use function array_merge; use function json_encode; @@ -41,8 +42,8 @@ class BuildClientSchema private $typeMap; /** - * @paran mixed[] $introspectionQuery - * @param bool[] $options + * @param mixed[] $introspectionQuery + * @param bool[] $options */ public function __construct(array $introspectionQuery, array $options = []) { @@ -71,9 +72,8 @@ public function __construct(array $introspectionQuery, array $options = []) * * Default: false * - * @param bool[] $options - * - * @throws Error + * @param mixed[] $introspectionQuery + * @param bool[] $options * * @api */ @@ -148,6 +148,9 @@ function (array $typeIntrospection) { return new Schema($schemaConfig); } + /** + * @param mixed[] $typeRef + */ private function getType(array $typeRef) : Type { if (isset($typeRef['kind'])) { diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index ba69daa53..5c1e2d9ef 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -34,24 +34,24 @@ */ class BuildClientSchemaTest extends TestCase { - protected static function assertCycleIntrospection(string $sdl): void + protected static function assertCycleIntrospection(string $sdl) : void { - $serverSchema = BuildSchema::build($sdl); + $serverSchema = BuildSchema::build($sdl); $initialIntrospection = Introspection::fromSchema($serverSchema); - $clientSchema = BuildClientSchema::build($initialIntrospection); - $secondIntrospection = Introspection::fromSchema($clientSchema); + $clientSchema = BuildClientSchema::build($initialIntrospection); + $secondIntrospection = Introspection::fromSchema($clientSchema); self::assertSame($initialIntrospection, $secondIntrospection); } - protected static function introspectionFromSDL(string $sdl): array + protected static function introspectionFromSDL(string $sdl) : array { $schema = BuildSchema::build($sdl); return Introspection::fromSchema($schema); } - protected static function clientSchemaFromSDL(string $sdl): Schema + protected static function clientSchemaFromSDL(string $sdl) : Schema { $introspection = self::introspectionFromSDL($sdl); @@ -81,9 +81,9 @@ public function testBuildsASimpleSchema() : void /** * it('builds a schema without the query type', () => { */ - public function testBuildsASchemaWithoutTheQueryType(): void + public function testBuildsASchemaWithoutTheQueryType() : void { - $sdl = ' + $sdl = ' type Query { foo: String } @@ -100,7 +100,7 @@ public function testBuildsASchemaWithoutTheQueryType(): void /** * it('builds a simple schema with all operation types', () => { */ - public function testBuildsASimpleSchemaWithAllOperationTypes(): void + public function testBuildsASimpleSchemaWithAllOperationTypes() : void { self::assertCycleIntrospection(' schema { @@ -132,7 +132,7 @@ public function testBuildsASimpleSchemaWithAllOperationTypes(): void /** * it('uses built-in scalars when possible', () => { */ - public function testUsesBuiltInScalarsWhenPossible(): void + public function testUsesBuiltInScalarsWhenPossible() : void { $sdl = ' scalar CustomScalar @@ -149,9 +149,9 @@ public function testUsesBuiltInScalarsWhenPossible(): void self::assertCycleIntrospection($sdl); - $schema = BuildSchema::build($sdl); + $schema = BuildSchema::build($sdl); $introspection = Introspection::fromSchema($schema); - $clientSchema = BuildClientSchema::build($introspection); + $clientSchema = BuildClientSchema::build($introspection); // Built-ins are used $this->assertSame(Type::int(), $clientSchema->getType('Int')); @@ -170,7 +170,7 @@ public function testUsesBuiltInScalarsWhenPossible(): void /** * it('includes standard types only if they are used', () => { */ - public function testIncludesStandardTypesOnlyIfTheyAreUsed(): void + public function testIncludesStandardTypesOnlyIfTheyAreUsed() : void { $clientSchema = self::clientSchemaFromSDL(' type Query { From 2b30fa7b3ac8dd8348ef5a12dc7e9c9080594cbb Mon Sep 17 00:00:00 2001 From: spawnia Date: Fri, 8 Nov 2019 20:31:17 +0100 Subject: [PATCH 13/28] Fix codestyle and static analysis --- src/Type/Introspection.php | 4 +- src/Utils/BuildClientSchema.php | 81 +++++++++++++++++++++------ src/Utils/Utils.php | 4 +- tests/Utils/BuildClientSchemaTest.php | 24 ++++---- 4 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index fbe4bbac6..334703cc8 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -199,9 +199,9 @@ public static function getTypes() * Whether to include descriptions in the introspection result. * Default: true * - * @param bool[]|bool $options + * @param array $options * - * @return mixed[]|null + * @return array|null */ public static function fromSchema(Schema $schema, array $options = []) : ?array { diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index aa54accca..1c6a28766 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -16,6 +16,7 @@ use GraphQL\Type\Definition\ListOfType; use GraphQL\Type\Definition\NamedType; use GraphQL\Type\Definition\NonNull; +use GraphQL\Type\Definition\NullableType; use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\OutputType; use GraphQL\Type\Definition\ScalarType; @@ -32,18 +33,18 @@ class BuildClientSchema { - /** @var mixed[] */ + /** @var array */ private $introspection; - /** @var bool[] */ + /** @var array */ private $options; - /** @var NamedType[] */ + /** @var array */ private $typeMap; /** - * @param mixed[] $introspectionQuery - * @param bool[] $options + * @param array $introspectionQuery + * @param array $options */ public function __construct(array $introspectionQuery, array $options = []) { @@ -72,8 +73,8 @@ public function __construct(array $introspectionQuery, array $options = []) * * Default: false * - * @param mixed[] $introspectionQuery - * @param bool[] $options + * @param array $introspectionQuery + * @param array $options * * @api */ @@ -84,7 +85,7 @@ public static function build(array $introspectionQuery, array $options = []) : S return $builder->buildSchema(); } - public function buildSchema() + public function buildSchema() : Schema { if (! array_key_exists('__schema', $this->introspection)) { throw new InvariantViolation('Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ' . json_encode($this->introspection)); @@ -149,7 +150,7 @@ function (array $typeIntrospection) { } /** - * @param mixed[] $typeRef + * @param array $typeRef */ private function getType(array $typeRef) : Type { @@ -168,11 +169,10 @@ private function getType(array $typeRef) : Type if (! $nullableRef) { throw new InvariantViolation('Decorated type deeper than introspection query.'); } + /** @var NullableType $nullableType */ $nullableType = $this->getType($nullableRef); - return new NonNull( - NonNull::assertNullableType($nullableType) - ); + return new NonNull($nullableType); } } @@ -183,6 +183,9 @@ private function getType(array $typeRef) : Type return $this->getNamedType($typeRef['name']); } + /** + * @return NamedType&Type + */ private function getNamedType(string $typeName) : NamedType { if (! isset($this->typeMap[$typeName])) { @@ -194,6 +197,9 @@ private function getNamedType(string $typeName) : NamedType return $this->typeMap[$typeName]; } + /** + * @param array $typeRef + */ private function getInputType(array $typeRef) : InputType { $type = $this->getType($typeRef); @@ -205,6 +211,9 @@ private function getInputType(array $typeRef) : InputType throw new InvariantViolation('Introspection must provide input type for arguments, but received: ' . json_encode($type)); } + /** + * @param array $typeRef + */ private function getOutputType(array $typeRef) : OutputType { $type = $this->getType($typeRef); @@ -216,6 +225,9 @@ private function getOutputType(array $typeRef) : OutputType throw new InvariantViolation('Introspection must provide output type for fields, but received: ' . json_encode($type)); } + /** + * @param array $typeRef + */ private function getObjectType(array $typeRef) : ObjectType { $type = $this->getType($typeRef); @@ -223,6 +235,9 @@ private function getObjectType(array $typeRef) : ObjectType return ObjectType::assertObjectType($type); } + /** + * @param array $typeRef + */ private function getInterfaceType(array $typeRef) : InterfaceType { $type = $this->getType($typeRef); @@ -230,6 +245,9 @@ private function getInterfaceType(array $typeRef) : InterfaceType return InterfaceType::assertInterfaceType($type); } + /** + * @param array $type + */ private function buildType(array $type) : NamedType { if (array_key_exists('name', $type) && array_key_exists('kind', $type)) { @@ -254,6 +272,9 @@ private function buildType(array $type) : NamedType ); } + /** + * @param array $scalar + */ private function buildScalarDef(array $scalar) : ScalarType { return new CustomScalarType([ @@ -262,6 +283,9 @@ private function buildScalarDef(array $scalar) : ScalarType ]); } + /** + * @param array $object + */ private function buildObjectDef(array $object) : ObjectType { if (! array_key_exists('interfaces', $object)) { @@ -283,6 +307,9 @@ private function buildObjectDef(array $object) : ObjectType ]); } + /** + * @param array $interface + */ private function buildInterfaceDef(array $interface) : InterfaceType { return new InterfaceType([ @@ -294,6 +321,9 @@ private function buildInterfaceDef(array $interface) : InterfaceType ]); } + /** + * @param array> $union + */ private function buildUnionDef(array $union) : UnionType { if (! array_key_exists('possibleType', $union)) { @@ -312,6 +342,9 @@ private function buildUnionDef(array $union) : UnionType ]); } + /** + * @param array> $enum + */ private function buildEnumDef(array $enum) : EnumType { if (! array_key_exists('enumValues', $enum)) { @@ -336,6 +369,9 @@ static function (array $enumValue) { ]); } + /** + * @param array $inputObject + */ private function buildInputObjectDef(array $inputObject) : InputObjectType { if (! array_key_exists('inputFields', $inputObject)) { @@ -346,11 +382,14 @@ private function buildInputObjectDef(array $inputObject) : InputObjectType 'name' => $inputObject['name'], 'description' => $inputObject['description'], 'fields' => function () use ($inputObject) { - return $this->buildInputValueDefMap($inputObject->inputFields); + return $this->buildInputValueDefMap($inputObject['inputFields']); }, ]); } + /** + * @param array $typeIntrospection + */ private function buildFieldDefMap(array $typeIntrospection) { if (! array_key_exists('fields', $typeIntrospection)) { @@ -378,11 +417,11 @@ function (array $fieldIntrospection) { } /** - * @param array[] $inputValueIntrospections + * @param array> $inputValueIntrospections * - * @return mixed[][] + * @return array> */ - private function buildInputValueDefMap(array $inputValueIntrospections) + private function buildInputValueDefMap(array $inputValueIntrospections) : array { return Utils::keyValMap( $inputValueIntrospections, @@ -393,7 +432,12 @@ static function (array $inputValue) : string { ); } - public function buildInputValue(array $inputValueIntrospection) + /** + * @param array $inputValueIntrospection + * + * @return array + */ + public function buildInputValue(array $inputValueIntrospection) : array { $type = $this->getInputType($inputValueIntrospection['type']); @@ -412,6 +456,9 @@ public function buildInputValue(array $inputValueIntrospection) return $inputValue; } + /** + * @param array $directive + */ public function buildDirective(array $directive) : Directive { if (! array_key_exists('args', $directive)) { diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 695591bb6..5e7ce6bac 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -272,9 +272,9 @@ public static function groupBy($traversable, callable $keyFn) } /** - * @param mixed[]|Traversable $traversable + * @param array|Traversable $traversable * - * @return mixed[][] + * @return array */ public static function keyValMap($traversable, callable $keyFn, callable $valFn) { diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 5c1e2d9ef..4bb7182d0 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -44,6 +44,9 @@ protected static function assertCycleIntrospection(string $sdl) : void self::assertSame($initialIntrospection, $secondIntrospection); } + /** + * @return array + */ protected static function introspectionFromSDL(string $sdl) : array { $schema = BuildSchema::build($sdl); @@ -92,9 +95,9 @@ public function testBuildsASchemaWithoutTheQueryType() : void unset($introspection['__schema']['queryType']); $clientSchema = BuildClientSchema::build($introspection); - $this->assertNull($clientSchema->getQueryType()); - $this->markTestSkipped('Why should this assertion be true?'); - $this->assertSame($sdl, SchemaPrinter::printIntrospectionSchema($clientSchema)); + self::assertNull($clientSchema->getQueryType()); + self::markTestSkipped('Why should this assertion be true?'); + self::assertSame($sdl, SchemaPrinter::printIntrospectionSchema($clientSchema)); } /** @@ -154,14 +157,14 @@ public function testUsesBuiltInScalarsWhenPossible() : void $clientSchema = BuildClientSchema::build($introspection); // Built-ins are used - $this->assertSame(Type::int(), $clientSchema->getType('Int')); - $this->assertSame(Type::float(), $clientSchema->getType('Float')); - $this->assertSame(Type::string(), $clientSchema->getType('String')); - $this->assertSame(Type::boolean(), $clientSchema->getType('Boolean')); - $this->assertSame(Type::id(), $clientSchema->getType('ID')); + self::assertSame(Type::int(), $clientSchema->getType('Int')); + self::assertSame(Type::float(), $clientSchema->getType('Float')); + self::assertSame(Type::string(), $clientSchema->getType('String')); + self::assertSame(Type::boolean(), $clientSchema->getType('Boolean')); + self::assertSame(Type::id(), $clientSchema->getType('ID')); // Custom are built - $this->assertNotSame( + self::assertNotSame( $schema->getType('CustomScalar'), $clientSchema->getType('CustomScalar') ); @@ -172,12 +175,13 @@ public function testUsesBuiltInScalarsWhenPossible() : void */ public function testIncludesStandardTypesOnlyIfTheyAreUsed() : void { + $this->markTestSkipped('Introspection currently does not follow the reference implementation.'); $clientSchema = self::clientSchemaFromSDL(' type Query { foo: String } '); - $this->assertNull($clientSchema->getType('Int')); + self::assertNull($clientSchema->getType('Int')); } } From 8f6f638ef79c8faed68dfc22cffeb34ad3b22d31 Mon Sep 17 00:00:00 2001 From: spawnia Date: Mon, 11 Nov 2019 21:47:04 +0100 Subject: [PATCH 14/28] Build some tests --- src/Type/Schema.php | 2 +- src/Utils/BuildClientSchema.php | 3 +- tests/Utils/BuildClientSchemaTest.php | 441 +++++++++++++++++++++++++- 3 files changed, 428 insertions(+), 18 deletions(-) diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 272e9ea44..dc780d782 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -239,7 +239,7 @@ private function collectAllTypes() */ public function getDirectives() { - return $this->config->directives ?: GraphQL::getStandardDirectives(); + return $this->config->directives ?? GraphQL::getStandardDirectives(); } /** diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 1c6a28766..9e725cbf3 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -4,7 +4,6 @@ namespace GraphQL\Utils; -use GraphQL\Error\Error; use GraphQL\Error\InvariantViolation; use GraphQL\Language\Parser; use GraphQL\Type\Definition\CustomScalarType; @@ -326,7 +325,7 @@ private function buildInterfaceDef(array $interface) : InterfaceType */ private function buildUnionDef(array $union) : UnionType { - if (! array_key_exists('possibleType', $union)) { + if (! array_key_exists('possibleTypes', $union)) { throw new InvariantViolation('Introspection result missing possibleTypes: ' . json_encode($union)); } diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 4bb7182d0..38d4cc152 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -4,33 +4,19 @@ namespace GraphQL\Tests\Utils; -use Closure; -use GraphQL\Error\Error; use GraphQL\GraphQL; -use GraphQL\Language\AST\EnumTypeDefinitionNode; -use GraphQL\Language\AST\InterfaceTypeDefinitionNode; -use GraphQL\Language\AST\ObjectTypeDefinitionNode; -use GraphQL\Language\Parser; -use GraphQL\Language\Printer; -use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; -use GraphQL\Type\Definition\InputObjectType; -use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; -use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; -use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Introspection; use GraphQL\Type\Schema; use GraphQL\Utils\BuildClientSchema; use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaPrinter; use PHPUnit\Framework\TestCase; -use function array_keys; -use function count; /** - * @see + * @see BuildClientSchema */ class BuildClientSchemaTest extends TestCase { @@ -184,4 +170,429 @@ public function testIncludesStandardTypesOnlyIfTheyAreUsed() : void self::assertNull($clientSchema->getType('Int')); } + + /** + * it('builds a schema with a recursive type reference', () => { + */ + public function testBuildsASchemaWithARecursiveTypeReference() : void + { + $this->assertCycleIntrospection(' + schema { + query: Recur + } + + type Recur { + recur: Recur + } + '); + } + + /** + * it('builds a schema with a circular type reference', () => { + */ + public function testBuildsASchemaWithACircularTypeReference() : void + { + $this->assertCycleIntrospection(' + type Dog { + bestFriend: Human + } + + type Human { + bestFriend: Dog + } + + type Query { + dog: Dog + human: Human + } + '); + } + + /** + * it('builds a schema with an interface', () => { + */ + public function testBuildsASchemaWithAnInterface() : void + { + $this->assertCycleIntrospection(' + type Dog implements Friendly { + bestFriend: Friendly + } + + interface Friendly { + """The best friend of this friendly thing""" + bestFriend: Friendly + } + + type Human implements Friendly { + bestFriend: Friendly + } + + type Query { + friendly: Friendly + } + '); + } + + /** + * it('builds a schema with an interface hierarchy', () => { + */ + public function testBuildsASchemaWithAnInterfaceHierarchy() : void + { + self::markTestSkipped('Will work only once intermediate interfaces are possible'); + $this->assertCycleIntrospection(' + type Dog implements Friendly & Named { + bestFriend: Friendly + name: String + } + + interface Friendly implements Named { + """The best friend of this friendly thing""" + bestFriend: Friendly + name: String + } + + type Human implements Friendly & Named { + bestFriend: Friendly + name: String + } + + interface Named { + name: String + } + + type Query { + friendly: Friendly + } + '); + } + + /** + * it('builds a schema with an implicit interface', () => { + */ + public function testBuildsASchemaWithAnImplicitInterface() : void + { + $this->assertCycleIntrospection(' + type Dog implements Friendly { + bestFriend: Friendly + } + + interface Friendly { + """The best friend of this friendly thing""" + bestFriend: Friendly + } + + type Query { + dog: Dog + } + '); + } + + /** + * it('builds a schema with a union', () => { + */ + public function testBuildsASchemaWithAUnion() : void + { + $this->assertCycleIntrospection(' + type Dog { + bestFriend: Friendly + } + + union Friendly = Dog | Human + + type Human { + bestFriend: Friendly + } + + type Query { + friendly: Friendly + } + '); + } + + /** + * it('builds a schema with complex field values', () => { + */ + public function testBuildsASchemaWithComplexFieldValues() : void + { + $this->assertCycleIntrospection(' + type Query { + string: String + listOfString: [String] + nonNullString: String! + nonNullListOfString: [String]! + nonNullListOfNonNullString: [String!]! + } + '); + } + + /** + * it('builds a schema with field arguments', () => { + */ + public function testBuildsASchemaWithFieldArguments() : void + { + $this->assertCycleIntrospection(' + type Query { + """A field with a single arg""" + one( + """This is an int arg""" + intArg: Int + ): String + + """A field with a two args""" + two( + """This is an list of int arg""" + listArg: [Int] + + """This is a required arg""" + requiredArg: Boolean! + ): String + } + '); + } + + /** + * it('builds a schema with default value on custom scalar field', () => { + */ + public function testBuildsASchemaWithDefaultValueOnCustomScalarField() : void + { + $this->assertCycleIntrospection(' + scalar CustomScalar + + type Query { + testField(testArg: CustomScalar = "default"): String + } + '); + } + + /** + * it('builds a schema with an enum', () => { + */ + public function testBuildsASchemaWithAnEnum(): void + { + $foodEnum = new EnumType([ + 'name' => 'Food', + 'description' => 'Varieties of food stuffs', + 'values' => [ + 'VEGETABLES' => [ + 'description' => 'Foods that are vegetables', + 'value' => 1, + ], + 'FRUITS' => [ + 'value' => 2, + ], + 'OILS' => [ + 'value' => 3, + 'deprecationReason' => 'Too fatty' + ] + ] + ]); + $schema = new Schema([ + 'query' => new ObjectType([ + 'name' => 'EnumFields', + 'fields' => [ + 'food' => [ + 'description' => 'Repeats the arg you give it', + 'type' => $foodEnum, + 'args' => [ + 'kind' => [ + 'description' => 'what kind of food?', + 'type' => $foodEnum, + ] + ] + ] + ] + ]) + ]); + + $introspection = Introspection::fromSchema($schema); + $clientSchema = BuildClientSchema::build($introspection); + + $introspectionFromClientSchema = Introspection::fromSchema($clientSchema); + self::assertSame($introspection, $introspectionFromClientSchema); + + /** @var EnumType $clientFoodEnum */ + $clientFoodEnum = $clientSchema->getType('Food'); + self::assertInstanceOf(EnumType::class, $clientFoodEnum); + + self::assertCount(3, $clientFoodEnum->getValues()); + + $vegetables = $clientFoodEnum->getValue('VEGETABLES'); + + // Client types do not get server-only values, so `value` mirrors `name`, + // rather than using the integers defined in the "server" schema. + self::assertSame('VEGETABLES', $vegetables->value); + self::assertSame('Foods that are vegetables', $vegetables->description); + self::assertFalse($vegetables->isDeprecated()); + self::assertNull($vegetables->deprecationReason); + self::assertNull($vegetables->astNode); + + $fruits = $clientFoodEnum->getValue('FRUITS'); + self::assertNull($fruits->description); + + $oils = $clientFoodEnum->getValue('OILS'); + self::assertTrue($oils->isDeprecated()); + self::assertSame('Too fatty', $oils->deprecationReason); + } + + /** + * it('builds a schema with an input object', () => { + */ + public function testBuildsASchemaWithAnInputObject(): void + { + self::assertCycleIntrospection(' + """An input address""" + input Address { + """What street is this address?""" + street: String! + + """The city the address is within?""" + city: String! + + """The country (blank will assume USA).""" + country: String = "USA" + } + + type Query { + """Get a geocode from an address""" + geocode( + """The address to lookup""" + address: Address + ): String + } + '); + } + + /** + * it('builds a schema with field arguments with default values', () => { + */ + public function testBuildsASchemaWithFieldArgumentsWithDefaultValues(): void + { + self::assertCycleIntrospection(' + input Geo { + lat: Float + lon: Float + } + + type Query { + defaultInt(intArg: Int = 30): String + defaultList(listArg: [Int] = [1, 2, 3]): String + defaultObject(objArg: Geo = {lat: 37.485, lon: -122.148}): String + defaultNull(intArg: Int = null): String + noDefault(intArg: Int): String + } + '); + } + + /** + * it('builds a schema with custom directives', () => { + */ + public function testBuildsASchemaWithCustomDirectives(): void + { + self::assertCycleIntrospection(' + """This is a custom directive""" + directive @customDirective on FIELD + + type Query { + string: String + } + '); + } + + /** + * it('builds a schema without directives', () => { + */ + public function testBuildsASchemaWithoutDirectives(): void + { + $sdl = <<getDirectives()); + self::assertSame([], $clientSchema->getDirectives()); + self::assertSame($sdl, SchemaPrinter::doPrint($clientSchema)); + } + + /** + * it('builds a schema aware of deprecation', () => { + */ + public function testBuildsASchemaAwareOfDeprecation(): void + { + self::assertCycleIntrospection(' + enum Color { + """So rosy""" + RED + + """So grassy""" + GREEN + + """So calming""" + BLUE + + """So sickening""" + MAUVE @deprecated(reason: "No longer in fashion") + } + + type Query { + """This is a shiny string field""" + shinyString: String + + """This is a deprecated string field""" + deprecatedString: String @deprecated(reason: "Use shinyString") + color: Color + } + '); + } + + /** + * it('builds a schema with empty deprecation reasons', () => { + */ + public function testBuildsASchemaWithEmptyDeprecationReasons(): void + { + self::assertCycleIntrospection(' + type Query { + someField: String @deprecated(reason: "") + } + + enum SomeEnum { + SOME_VALUE @deprecated(reason: "") + } + '); + } + + /** + * it('can use client schema for limited execution', () => { + */ + public function testUseClientSchemaForLimitedExecution(): void + { + $schema = BuildSchema::build(' + scalar CustomScalar + + type Query { + foo(custom1: CustomScalar, custom2: CustomScalar): String + } + '); + + $introspection = Introspection::fromSchema($schema); + $clientSchema = BuildClientSchema::build($introspection); + + $result = GraphQL::executeQuery( + $clientSchema, + 'query Limited($v: CustomScalar) { foo(custom1: 123, custom2: $v) }', + ['foo' => 'bar', 'unused' => 'value'], + null, + ['v' => 'baz'] + ); + + self::assertSame(['foo' => 'bar'], $result->data); + } } From 6d0fb287934abd34caa6705d57e2b98b762b151f Mon Sep 17 00:00:00 2001 From: spawnia Date: Sat, 16 Nov 2019 11:46:54 +0100 Subject: [PATCH 15/28] Add more tests --- src/Utils/BuildClientSchema.php | 28 +++--- tests/Utils/BuildClientSchemaTest.php | 126 ++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 21 deletions(-) diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 9e725cbf3..e50c5d26a 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -87,7 +87,7 @@ public static function build(array $introspectionQuery, array $options = []) : S public function buildSchema() : Schema { if (! array_key_exists('__schema', $this->introspection)) { - throw new InvariantViolation('Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ' . json_encode($this->introspection)); + throw new InvariantViolation('Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ' . json_encode($this->introspection) . '.'); } $schemaIntrospection = $this->introspection['__schema']; @@ -175,8 +175,8 @@ private function getType(array $typeRef) : Type } } - if (! $typeRef['name']) { - throw new InvariantViolation('Unknown type reference: ' . json_encode($typeRef)); + if (! isset($typeRef['name'])) { + throw new InvariantViolation('Unknown type reference: ' . json_encode($typeRef) . '.'); } return $this->getNamedType($typeRef['name']); @@ -207,7 +207,7 @@ private function getInputType(array $typeRef) : InputType return $type; } - throw new InvariantViolation('Introspection must provide input type for arguments, but received: ' . json_encode($type)); + throw new InvariantViolation('Introspection must provide input type for arguments, but received: ' . json_encode($type) . '.'); } /** @@ -221,7 +221,7 @@ private function getOutputType(array $typeRef) : OutputType return $type; } - throw new InvariantViolation('Introspection must provide output type for fields, but received: ' . json_encode($type)); + throw new InvariantViolation('Introspection must provide output type for fields, but received: ' . json_encode($type) . '.'); } /** @@ -267,7 +267,7 @@ private function buildType(array $type) : NamedType } throw new InvariantViolation( - 'Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: ' . json_encode($type) + 'Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: ' . json_encode($type) . '.' ); } @@ -288,7 +288,7 @@ private function buildScalarDef(array $scalar) : ScalarType private function buildObjectDef(array $object) : ObjectType { if (! array_key_exists('interfaces', $object)) { - throw new InvariantViolation('Introspection result missing interfaces: ' . json_encode($object)); + throw new InvariantViolation('Introspection result missing interfaces: ' . json_encode($object) . '.'); } return new ObjectType([ @@ -326,7 +326,7 @@ private function buildInterfaceDef(array $interface) : InterfaceType private function buildUnionDef(array $union) : UnionType { if (! array_key_exists('possibleTypes', $union)) { - throw new InvariantViolation('Introspection result missing possibleTypes: ' . json_encode($union)); + throw new InvariantViolation('Introspection result missing possibleTypes: ' . json_encode($union) . '.'); } return new UnionType([ @@ -347,7 +347,7 @@ private function buildUnionDef(array $union) : UnionType private function buildEnumDef(array $enum) : EnumType { if (! array_key_exists('enumValues', $enum)) { - throw new InvariantViolation('Introspection result missing enumValues: ' . json_encode($enum)); + throw new InvariantViolation('Introspection result missing enumValues: ' . json_encode($enum) . '.'); } return new EnumType([ @@ -374,7 +374,7 @@ static function (array $enumValue) { private function buildInputObjectDef(array $inputObject) : InputObjectType { if (! array_key_exists('inputFields', $inputObject)) { - throw new InvariantViolation('Introspection result missing inputFields: ' . json_encode($inputObject)); + throw new InvariantViolation('Introspection result missing inputFields: ' . json_encode($inputObject) . '.'); } return new InputObjectType([ @@ -392,7 +392,7 @@ private function buildInputObjectDef(array $inputObject) : InputObjectType private function buildFieldDefMap(array $typeIntrospection) { if (! array_key_exists('fields', $typeIntrospection)) { - throw new InvariantViolation('Introspection result missing fields: ' . json_encode($typeIntrospection)); + throw new InvariantViolation('Introspection result missing fields: ' . json_encode($typeIntrospection) . '.'); } return Utils::keyValMap( @@ -402,7 +402,7 @@ static function (array $fieldIntrospection) : string { }, function (array $fieldIntrospection) { if (! array_key_exists('args', $fieldIntrospection)) { - throw new InvariantViolation('Introspection result missing field args: ' . json_encode($fieldIntrospection)); + throw new InvariantViolation('Introspection result missing field args: ' . json_encode($fieldIntrospection) . '.'); } return [ @@ -461,10 +461,10 @@ public function buildInputValue(array $inputValueIntrospection) : array public function buildDirective(array $directive) : Directive { if (! array_key_exists('args', $directive)) { - throw new InvariantViolation('Introspection result missing directive args: ' . json_encode($directive)); + throw new InvariantViolation('Introspection result missing directive args: ' . json_encode($directive) . '.'); } if (! array_key_exists('locations', $directive)) { - throw new InvariantViolation('Introspection result missing directive locations: ' . json_encode($directive)); + throw new InvariantViolation('Introspection result missing directive locations: ' . json_encode($directive) . '.'); } return new Directive([ diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 38d4cc152..f9c4637f3 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -13,6 +13,7 @@ use GraphQL\Utils\BuildClientSchema; use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaPrinter; +use GraphQL\Utils\Utils; use PHPUnit\Framework\TestCase; /** @@ -72,18 +73,18 @@ public function testBuildsASimpleSchema() : void */ public function testBuildsASchemaWithoutTheQueryType() : void { - $sdl = ' - type Query { - foo: String - } - '; + $sdl = <<getQueryType()); - self::markTestSkipped('Why should this assertion be true?'); - self::assertSame($sdl, SchemaPrinter::printIntrospectionSchema($clientSchema)); + self::assertSame($sdl, SchemaPrinter::doPrint($clientSchema)); } /** @@ -595,4 +596,115 @@ public function testUseClientSchemaForLimitedExecution(): void self::assertSame(['foo' => 'bar'], $result->data); } + + // describe('throws when given invalid introspection', () => { + + protected function dummySchema(): Schema + { + return BuildSchema::build(' + type Query { + foo(bar: String): String + } + + interface SomeInterface { + foo: String + } + + union SomeUnion = Query + + enum SomeEnum { FOO } + + input SomeInputObject { + foo: String + } + + directive @SomeDirective on QUERY + '); + } + + /** + * it('throws when introspection is missing __schema property', () => { + */ + public function testThrowsWhenIntrospectionIsMissing__schemaProperty(): void + { + $this->expectExceptionMessage('Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: [].'); + BuildClientSchema::build([]); + } + + /** + * it('throws when referenced unknown type', () => { + */ + public function testThrowsWhenReferencedUnknownType(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + + $introspection['__schema']['types'] = array_filter( + $introspection['__schema']['types'], + static function (array $type): bool { + return $type['name'] !== 'Query'; + } + ); + + $this->expectExceptionMessage('Invalid or incomplete schema, unknown type: Query. Ensure that a full introspection query is used in order to build a client schema.'); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing definition for one of the standard scalars', () => { + */ + public function testThrowsWhenMissingDefinitionForOneOfTheStandardScalars(): void + { + $schema = BuildSchema::build(' + type Query { + foo: Float + } + '); + $introspection = Introspection::fromSchema($schema); + + $introspection['__schema']['types'] = array_filter( + $introspection['__schema']['types'], + static function (array $type): bool { + return $type['name'] !== 'Float'; + } + ); + + $this->expectExceptionMessage('Invalid or incomplete schema, unknown type: Float. Ensure that a full introspection query is used in order to build a client schema.'); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when type reference is missing name', () => { + */ + public function testThrowsWhenTypeReferenceIsMissingName(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + + $this->assertNotEmpty($introspection['__schema']['queryType']['name']); + + unset($introspection['__schema']['queryType']['name']); + + $this->expectExceptionMessage('Unknown type reference: [].'); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing kind', () => { + */ + public function testThrowsWhenMissingKind(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + $queryTypeIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'Query'){ + $queryTypeIntrospection = &$type; + } + } + + $this->assertArrayHasKey('kind', $queryTypeIntrospection); + + unset($queryTypeIntrospection['kind']); + + $this->expectExceptionMessageRegExp('/Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: {"name":"Query",.*}\./'); + BuildClientSchema::build($introspection); + } } From c8560f0b4269b3fc70c8a730248b137ccfffd1e6 Mon Sep 17 00:00:00 2001 From: spawnia Date: Tue, 19 Nov 2019 19:51:16 +0100 Subject: [PATCH 16/28] Fix test and implementation --- src/Type/Definition/FieldArgument.php | 10 +- src/Utils/BuildClientSchema.php | 16 +- tests/Utils/BuildClientSchemaTest.php | 360 +++++++++++++++++++++++++- 3 files changed, 373 insertions(+), 13 deletions(-) diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index 895504c94..ddb2e025d 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -32,6 +32,13 @@ class FieldArgument /** @var InputType&Type */ private $type; + /** + * Helps to differentiate when `defaultValue` is `null` and when it was not even set initially + * + * @var bool + */ + private $defaultValueExists = false; + /** * @param mixed[] $def */ @@ -47,6 +54,7 @@ public function __construct(array $def) break; case 'defaultValue': $this->defaultValue = $value; + $this->defaultValueExists = true; break; case 'description': $this->description = $value; @@ -87,7 +95,7 @@ public function getType() : Type public function defaultValueExists() : bool { - return array_key_exists('defaultValue', $this->config); + return $this->defaultValueExists; } public function assertValid(FieldDefinition $parentField, Type $parentType) diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index e50c5d26a..082a180fa 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -155,21 +155,19 @@ private function getType(array $typeRef) : Type { if (isset($typeRef['kind'])) { if ($typeRef['kind'] === TypeKind::LIST) { - $itemRef = $typeRef['ofType']; - if (! $itemRef) { + if (! isset($typeRef['ofType'])) { throw new InvariantViolation('Decorated type deeper than introspection query.'); } - return new ListOfType($this->getType($itemRef)); + return new ListOfType($this->getType($typeRef['ofType'])); } if ($typeRef['kind'] === TypeKind::NON_NULL) { - $nullableRef = $typeRef['ofType']; - if (! $nullableRef) { + if (! isset($typeRef['ofType'])) { throw new InvariantViolation('Decorated type deeper than introspection query.'); } /** @var NullableType $nullableType */ - $nullableType = $this->getType($nullableRef); + $nullableType = $this->getType($typeRef['ofType']); return new NonNull($nullableType); } @@ -279,6 +277,9 @@ private function buildScalarDef(array $scalar) : ScalarType return new CustomScalarType([ 'name' => $scalar['name'], 'description' => $scalar['description'], + 'serialize' => static function ($value): string { + return (string) $value; + } ]); } @@ -297,7 +298,8 @@ private function buildObjectDef(array $object) : ObjectType 'interfaces' => function () use ($object) { return array_map( [$this, 'getInterfaceType'], - $object['interfaces'] + // Legacy support for interfaces with null as interfaces field + $object['interfaces'] ?? [] ); }, 'fields' => function () use ($object) { diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index f9c4637f3..3707ae581 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -13,7 +13,6 @@ use GraphQL\Utils\BuildClientSchema; use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaPrinter; -use GraphQL\Utils\Utils; use PHPUnit\Framework\TestCase; /** @@ -627,7 +626,9 @@ enum SomeEnum { FOO } */ public function testThrowsWhenIntrospectionIsMissing__schemaProperty(): void { - $this->expectExceptionMessage('Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: [].'); + $this->expectExceptionMessage( + 'Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: [].' + ); BuildClientSchema::build([]); } @@ -645,7 +646,9 @@ static function (array $type): bool { } ); - $this->expectExceptionMessage('Invalid or incomplete schema, unknown type: Query. Ensure that a full introspection query is used in order to build a client schema.'); + $this->expectExceptionMessage( + 'Invalid or incomplete schema, unknown type: Query. Ensure that a full introspection query is used in order to build a client schema.' + ); BuildClientSchema::build($introspection); } @@ -668,7 +671,9 @@ static function (array $type): bool { } ); - $this->expectExceptionMessage('Invalid or incomplete schema, unknown type: Float. Ensure that a full introspection query is used in order to build a client schema.'); + $this->expectExceptionMessage( + 'Invalid or incomplete schema, unknown type: Float. Ensure that a full introspection query is used in order to build a client schema.' + ); BuildClientSchema::build($introspection); } @@ -704,7 +709,352 @@ public function testThrowsWhenMissingKind(): void unset($queryTypeIntrospection['kind']); - $this->expectExceptionMessageRegExp('/Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: {"name":"Query",.*}\./'); + $this->expectExceptionMessageRegExp( + '/Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: {"name":"Query",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing interfaces', () => { + */ + public function testThrowsWhenMissingInterfaces(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + $queryTypeIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'Query'){ + $queryTypeIntrospection = &$type; + } + } + + $this->assertArrayHasKey('interfaces', $queryTypeIntrospection); + + unset($queryTypeIntrospection['interfaces']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing interfaces: {"kind":"OBJECT","name":"Query",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('Legacy support for interfaces with null as interfaces field', () => { + */ + public function testLegacySupportForInterfacesWithNullAsInterfacesField(): void + { + $dummySchema = $this->dummySchema(); + $introspection = Introspection::fromSchema($dummySchema); + $queryTypeIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'Query'){ + $queryTypeIntrospection = &$type; + } + } + + $this->assertArrayHasKey('interfaces', $queryTypeIntrospection); + + $queryTypeIntrospection['interfaces'] = null; + + $clientSchema = BuildClientSchema::build($introspection); + $this->assertSame( + SchemaPrinter::doPrint($dummySchema), + SchemaPrinter::doPrint($clientSchema) + ); + } + + /** + * it('throws when missing fields', () => { + */ + public function testThrowsWhenMissingFields(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + $queryTypeIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'Query'){ + $queryTypeIntrospection = &$type; + } + } + + $this->assertArrayHasKey('fields', $queryTypeIntrospection); + + unset($queryTypeIntrospection['fields']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing fields: {"kind":"OBJECT","name":"Query",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing field args', () => { + */ + public function testThrowsWhenMissingFieldArgs(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + $queryTypeIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'Query'){ + $queryTypeIntrospection = &$type; + } + } + + $firstField = &$queryTypeIntrospection['fields'][0]; + $this->assertArrayHasKey('args', $firstField); + + unset($firstField['args']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing field args: {"name":"foo",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when output type is used as an arg type', () => { + */ + public function testThrowsWhenOutputTypeIsUsedAsAnArgType(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + $queryTypeIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'Query'){ + $queryTypeIntrospection = &$type; + } + } + + $firstArgType = &$queryTypeIntrospection['fields'][0]['args'][0]['type']; + $this->assertArrayHasKey('name', $firstArgType); + + $firstArgType['name'] = 'SomeUnion'; + + $this->expectExceptionMessage( + 'Introspection must provide input type for arguments, but received: "SomeUnion".' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when input type is used as a field type', () => { + */ + public function testThrowsWhenInputTypeIsUsedAsAFieldType(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + $queryTypeIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'Query'){ + $queryTypeIntrospection = &$type; + } + } + + $firstFieldType = &$queryTypeIntrospection['fields'][0]['type']; + $this->assertArrayHasKey('name', $firstFieldType); + + $firstFieldType['name'] = 'SomeInputObject'; + + $this->expectExceptionMessage( + 'Introspection must provide output type for fields, but received: "SomeInputObject".' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing possibleTypes', () => { + */ + public function testThrowsWhenMissingPossibleTypes(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + $someUnionIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'SomeUnion'){ + $someUnionIntrospection = &$type; + } + } + + $this->assertArrayHasKey('possibleTypes', $someUnionIntrospection); + + unset($someUnionIntrospection['possibleTypes']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing possibleTypes: {"kind":"UNION","name":"SomeUnion",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing enumValues', () => { + */ + public function testThrowsWhenMissingEnumValues(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + $someEnumIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'SomeEnum'){ + $someEnumIntrospection = &$type; + } + } + + $this->assertArrayHasKey('enumValues', $someEnumIntrospection); + + unset($someEnumIntrospection['enumValues']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing enumValues: {"kind":"ENUM","name":"SomeEnum",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing inputFields', () => { + */ + public function testThrowsWhenMissingInputFields(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + $someInputObjectIntrospection = null; + foreach($introspection['__schema']['types'] as &$type) { + if($type['name'] === 'SomeInputObject'){ + $someInputObjectIntrospection = &$type; + } + } + + $this->assertArrayHasKey('inputFields', $someInputObjectIntrospection); + + unset($someInputObjectIntrospection['inputFields']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing inputFields: {"kind":"INPUT_OBJECT","name":"SomeInputObject",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing directive locations', () => { + */ + public function testThrowsWhenMissingDirectiveLocations(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + + $someDirectiveIntrospection = &$introspection['__schema']['directives'][0]; + $this->assertSame('SomeDirective', $someDirectiveIntrospection['name']); + $this->assertSame(['QUERY'], $someDirectiveIntrospection['locations']); + + unset($someDirectiveIntrospection['locations']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing directive locations: {"name":"SomeDirective",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('throws when missing directive args', () => { + */ + public function testThrowsWhenMissingDirectiveArgs(): void + { + $introspection = Introspection::fromSchema($this->dummySchema()); + + $someDirectiveIntrospection = &$introspection['__schema']['directives'][0]; + $this->assertSame('SomeDirective', $someDirectiveIntrospection['name']); + $this->assertSame([], $someDirectiveIntrospection['args']); + + unset($someDirectiveIntrospection['args']); + + $this->expectExceptionMessageRegExp( + '/Introspection result missing directive args: {"name":"SomeDirective",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + // describe('very deep decorators are not supported', () => { + + /** + * it('fails on very deep (> 7 levels) lists', () => { + */ + public function testFailsOnVeryDeepListsWithMoreThan7Levels(): void + { + $schema = BuildSchema::build(' + type Query { + foo: [[[[[[[[String]]]]]]]] + } + '); + $introspection = Introspection::fromSchema($schema); + + $this->expectExceptionMessage( + 'Decorated type deeper than introspection query.' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('fails on very deep (> 7 levels) non-null', () => { + */ + public function testFailsOnVeryDeepNonNullWithMoreThan7Levels(): void + { + $schema = BuildSchema::build(' + type Query { + foo: [[[[String!]!]!]!] + } + '); + $introspection = Introspection::fromSchema($schema); + + $this->expectExceptionMessage( + 'Decorated type deeper than introspection query.' + ); + BuildClientSchema::build($introspection); + } + + /** + * it('succeeds on deep (<= 7 levels) types', () => { + */ + public function testSucceedsOnDeepTypesWithMoreThanOrEqualTo7Levels(): void + { + // e.g., fully non-null 3D matrix + $this->assertCycleIntrospection(' + type Query { + foo: [[[String!]!]!]! + } + '); + } + + // describe('prevents infinite recursion on invalid introspection', () => { + + /** + * it('recursive interfaces', () => { + */ + public function testRecursiveInterfaces(): void + { + $sdl = ' + type Query { + foo: Foo + } + + type Foo implements Foo { + foo: String + } + '; + $schema = BuildSchema::build($sdl); + $introspection = Introspection::fromSchema($schema); + + $this->expectExceptionMessage('Expected Foo to be a GraphQL Interface type.'); + BuildClientSchema::build($introspection); + } + + /** + * it('recursive union', () => { + */ + public function testRecursiveUnion(): void + { + $sdl = ' + type Query { + foo: Foo + } + + union Foo = Foo + '; + $schema = BuildSchema::build($sdl); + $introspection = Introspection::fromSchema($schema); + + $this->expectExceptionMessage('Expected Foo to be a GraphQL Object type.'); BuildClientSchema::build($introspection); } } From b5cc5fd4bdcfdc45300c34dc1dfc4137cad23b44 Mon Sep 17 00:00:00 2001 From: spawnia Date: Tue, 19 Nov 2019 19:54:44 +0100 Subject: [PATCH 17/28] Fix codestyle --- composer.json | 2 +- src/Type/Definition/FieldArgument.php | 2 +- src/Utils/BuildClientSchema.php | 4 +- tests/Utils/BuildClientSchemaTest.php | 309 ++++++++++++++------------ 4 files changed, 168 insertions(+), 149 deletions(-) diff --git a/composer.json b/composer.json index f27b8c9d4..a3fe8b3f0 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "test": "phpunit", "lint" : "phpcs", "fix" : "phpcbf", - "stan": "phpstan analyse --ansi --memory-limit 256M", + "stan": "phpstan analyse --ansi --memory-limit 512M", "check": "composer lint && composer stan && composer test" } } diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index ddb2e025d..e9ff86ba2 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -53,7 +53,7 @@ public function __construct(array $def) $this->name = $value; break; case 'defaultValue': - $this->defaultValue = $value; + $this->defaultValue = $value; $this->defaultValueExists = true; break; case 'description': diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 082a180fa..f7c4c5e1e 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -277,9 +277,9 @@ private function buildScalarDef(array $scalar) : ScalarType return new CustomScalarType([ 'name' => $scalar['name'], 'description' => $scalar['description'], - 'serialize' => static function ($value): string { + 'serialize' => static function ($value) : string { return (string) $value; - } + }, ]); } diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 3707ae581..ac0870b3f 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -14,6 +14,7 @@ use GraphQL\Utils\BuildSchema; use GraphQL\Utils\SchemaPrinter; use PHPUnit\Framework\TestCase; +use function array_filter; /** * @see BuildClientSchema @@ -161,7 +162,7 @@ public function testUsesBuiltInScalarsWhenPossible() : void */ public function testIncludesStandardTypesOnlyIfTheyAreUsed() : void { - $this->markTestSkipped('Introspection currently does not follow the reference implementation.'); + self::markTestSkipped('Introspection currently does not follow the reference implementation.'); $clientSchema = self::clientSchemaFromSDL(' type Query { foo: String @@ -176,7 +177,7 @@ public function testIncludesStandardTypesOnlyIfTheyAreUsed() : void */ public function testBuildsASchemaWithARecursiveTypeReference() : void { - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' schema { query: Recur } @@ -192,7 +193,7 @@ public function testBuildsASchemaWithARecursiveTypeReference() : void */ public function testBuildsASchemaWithACircularTypeReference() : void { - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' type Dog { bestFriend: Human } @@ -213,7 +214,7 @@ public function testBuildsASchemaWithACircularTypeReference() : void */ public function testBuildsASchemaWithAnInterface() : void { - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' type Dog implements Friendly { bestFriend: Friendly } @@ -239,7 +240,7 @@ interface Friendly { public function testBuildsASchemaWithAnInterfaceHierarchy() : void { self::markTestSkipped('Will work only once intermediate interfaces are possible'); - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' type Dog implements Friendly & Named { bestFriend: Friendly name: String @@ -271,7 +272,7 @@ interface Named { */ public function testBuildsASchemaWithAnImplicitInterface() : void { - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' type Dog implements Friendly { bestFriend: Friendly } @@ -292,7 +293,7 @@ interface Friendly { */ public function testBuildsASchemaWithAUnion() : void { - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' type Dog { bestFriend: Friendly } @@ -314,7 +315,7 @@ public function testBuildsASchemaWithAUnion() : void */ public function testBuildsASchemaWithComplexFieldValues() : void { - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' type Query { string: String listOfString: [String] @@ -330,7 +331,7 @@ public function testBuildsASchemaWithComplexFieldValues() : void */ public function testBuildsASchemaWithFieldArguments() : void { - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' type Query { """A field with a single arg""" one( @@ -355,7 +356,7 @@ public function testBuildsASchemaWithFieldArguments() : void */ public function testBuildsASchemaWithDefaultValueOnCustomScalarField() : void { - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' scalar CustomScalar type Query { @@ -367,7 +368,7 @@ public function testBuildsASchemaWithDefaultValueOnCustomScalarField() : void /** * it('builds a schema with an enum', () => { */ - public function testBuildsASchemaWithAnEnum(): void + public function testBuildsASchemaWithAnEnum() : void { $foodEnum = new EnumType([ 'name' => 'Food', @@ -377,16 +378,14 @@ public function testBuildsASchemaWithAnEnum(): void 'description' => 'Foods that are vegetables', 'value' => 1, ], - 'FRUITS' => [ - 'value' => 2, - ], + 'FRUITS' => ['value' => 2], 'OILS' => [ 'value' => 3, - 'deprecationReason' => 'Too fatty' - ] - ] + 'deprecationReason' => 'Too fatty', + ], + ], ]); - $schema = new Schema([ + $schema = new Schema([ 'query' => new ObjectType([ 'name' => 'EnumFields', 'fields' => [ @@ -397,15 +396,15 @@ public function testBuildsASchemaWithAnEnum(): void 'kind' => [ 'description' => 'what kind of food?', 'type' => $foodEnum, - ] - ] - ] - ] - ]) + ], + ], + ], + ], + ]), ]); $introspection = Introspection::fromSchema($schema); - $clientSchema = BuildClientSchema::build($introspection); + $clientSchema = BuildClientSchema::build($introspection); $introspectionFromClientSchema = Introspection::fromSchema($clientSchema); self::assertSame($introspection, $introspectionFromClientSchema); @@ -437,7 +436,7 @@ public function testBuildsASchemaWithAnEnum(): void /** * it('builds a schema with an input object', () => { */ - public function testBuildsASchemaWithAnInputObject(): void + public function testBuildsASchemaWithAnInputObject() : void { self::assertCycleIntrospection(' """An input address""" @@ -465,7 +464,7 @@ public function testBuildsASchemaWithAnInputObject(): void /** * it('builds a schema with field arguments with default values', () => { */ - public function testBuildsASchemaWithFieldArgumentsWithDefaultValues(): void + public function testBuildsASchemaWithFieldArgumentsWithDefaultValues() : void { self::assertCycleIntrospection(' input Geo { @@ -486,7 +485,7 @@ public function testBuildsASchemaWithFieldArgumentsWithDefaultValues(): void /** * it('builds a schema with custom directives', () => { */ - public function testBuildsASchemaWithCustomDirectives(): void + public function testBuildsASchemaWithCustomDirectives() : void { self::assertCycleIntrospection(' """This is a custom directive""" @@ -501,7 +500,7 @@ public function testBuildsASchemaWithCustomDirectives(): void /** * it('builds a schema without directives', () => { */ - public function testBuildsASchemaWithoutDirectives(): void + public function testBuildsASchemaWithoutDirectives() : void { $sdl = << { */ - public function testBuildsASchemaAwareOfDeprecation(): void + public function testBuildsASchemaAwareOfDeprecation() : void { self::assertCycleIntrospection(' enum Color { @@ -556,7 +555,7 @@ enum Color { /** * it('builds a schema with empty deprecation reasons', () => { */ - public function testBuildsASchemaWithEmptyDeprecationReasons(): void + public function testBuildsASchemaWithEmptyDeprecationReasons() : void { self::assertCycleIntrospection(' type Query { @@ -572,7 +571,7 @@ enum SomeEnum { /** * it('can use client schema for limited execution', () => { */ - public function testUseClientSchemaForLimitedExecution(): void + public function testUseClientSchemaForLimitedExecution() : void { $schema = BuildSchema::build(' scalar CustomScalar @@ -583,7 +582,7 @@ public function testUseClientSchemaForLimitedExecution(): void '); $introspection = Introspection::fromSchema($schema); - $clientSchema = BuildClientSchema::build($introspection); + $clientSchema = BuildClientSchema::build($introspection); $result = GraphQL::executeQuery( $clientSchema, @@ -598,7 +597,7 @@ public function testUseClientSchemaForLimitedExecution(): void // describe('throws when given invalid introspection', () => { - protected function dummySchema(): Schema + protected static function dummySchema() : Schema { return BuildSchema::build(' type Query { @@ -624,9 +623,9 @@ enum SomeEnum { FOO } /** * it('throws when introspection is missing __schema property', () => { */ - public function testThrowsWhenIntrospectionIsMissing__schemaProperty(): void + public function testThrowsWhenIntrospectionIsMissing__schemaProperty() : void { - $this->expectExceptionMessage( + self::expectExceptionMessage( 'Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: [].' ); BuildClientSchema::build([]); @@ -635,18 +634,18 @@ public function testThrowsWhenIntrospectionIsMissing__schemaProperty(): void /** * it('throws when referenced unknown type', () => { */ - public function testThrowsWhenReferencedUnknownType(): void + public function testThrowsWhenReferencedUnknownType() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $introspection['__schema']['types'] = array_filter( $introspection['__schema']['types'], - static function (array $type): bool { + static function (array $type) : bool { return $type['name'] !== 'Query'; } ); - $this->expectExceptionMessage( + self::expectExceptionMessage( 'Invalid or incomplete schema, unknown type: Query. Ensure that a full introspection query is used in order to build a client schema.' ); BuildClientSchema::build($introspection); @@ -655,9 +654,9 @@ static function (array $type): bool { /** * it('throws when missing definition for one of the standard scalars', () => { */ - public function testThrowsWhenMissingDefinitionForOneOfTheStandardScalars(): void + public function testThrowsWhenMissingDefinitionForOneOfTheStandardScalars() : void { - $schema = BuildSchema::build(' + $schema = BuildSchema::build(' type Query { foo: Float } @@ -666,12 +665,12 @@ public function testThrowsWhenMissingDefinitionForOneOfTheStandardScalars(): voi $introspection['__schema']['types'] = array_filter( $introspection['__schema']['types'], - static function (array $type): bool { + static function (array $type) : bool { return $type['name'] !== 'Float'; } ); - $this->expectExceptionMessage( + self::expectExceptionMessage( 'Invalid or incomplete schema, unknown type: Float. Ensure that a full introspection query is used in order to build a client schema.' ); BuildClientSchema::build($introspection); @@ -680,36 +679,38 @@ static function (array $type): bool { /** * it('throws when type reference is missing name', () => { */ - public function testThrowsWhenTypeReferenceIsMissingName(): void + public function testThrowsWhenTypeReferenceIsMissingName() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); - $this->assertNotEmpty($introspection['__schema']['queryType']['name']); + self::assertNotEmpty($introspection['__schema']['queryType']['name']); unset($introspection['__schema']['queryType']['name']); - $this->expectExceptionMessage('Unknown type reference: [].'); + self::expectExceptionMessage('Unknown type reference: [].'); BuildClientSchema::build($introspection); } /** * it('throws when missing kind', () => { */ - public function testThrowsWhenMissingKind(): void + public function testThrowsWhenMissingKind() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $queryTypeIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'Query'){ - $queryTypeIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; } + + $queryTypeIntrospection = &$type; } - $this->assertArrayHasKey('kind', $queryTypeIntrospection); + self::assertArrayHasKey('kind', $queryTypeIntrospection); unset($queryTypeIntrospection['kind']); - $this->expectExceptionMessageRegExp( + self::expectExceptionMessageRegExp( '/Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: {"name":"Query",.*}\./' ); BuildClientSchema::build($introspection); @@ -718,21 +719,23 @@ public function testThrowsWhenMissingKind(): void /** * it('throws when missing interfaces', () => { */ - public function testThrowsWhenMissingInterfaces(): void + public function testThrowsWhenMissingInterfaces() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $queryTypeIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'Query'){ - $queryTypeIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; } + + $queryTypeIntrospection = &$type; } - $this->assertArrayHasKey('interfaces', $queryTypeIntrospection); + self::assertArrayHasKey('interfaces', $queryTypeIntrospection); unset($queryTypeIntrospection['interfaces']); - $this->expectExceptionMessageRegExp( + self::expectExceptionMessageRegExp( '/Introspection result missing interfaces: {"kind":"OBJECT","name":"Query",.*}\./' ); BuildClientSchema::build($introspection); @@ -741,23 +744,25 @@ public function testThrowsWhenMissingInterfaces(): void /** * it('Legacy support for interfaces with null as interfaces field', () => { */ - public function testLegacySupportForInterfacesWithNullAsInterfacesField(): void + public function testLegacySupportForInterfacesWithNullAsInterfacesField() : void { - $dummySchema = $this->dummySchema(); - $introspection = Introspection::fromSchema($dummySchema); + $dummySchema = self::dummySchema(); + $introspection = Introspection::fromSchema($dummySchema); $queryTypeIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'Query'){ - $queryTypeIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; } + + $queryTypeIntrospection = &$type; } - $this->assertArrayHasKey('interfaces', $queryTypeIntrospection); + self::assertArrayHasKey('interfaces', $queryTypeIntrospection); $queryTypeIntrospection['interfaces'] = null; $clientSchema = BuildClientSchema::build($introspection); - $this->assertSame( + self::assertSame( SchemaPrinter::doPrint($dummySchema), SchemaPrinter::doPrint($clientSchema) ); @@ -766,21 +771,23 @@ public function testLegacySupportForInterfacesWithNullAsInterfacesField(): void /** * it('throws when missing fields', () => { */ - public function testThrowsWhenMissingFields(): void + public function testThrowsWhenMissingFields() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $queryTypeIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'Query'){ - $queryTypeIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; } + + $queryTypeIntrospection = &$type; } - $this->assertArrayHasKey('fields', $queryTypeIntrospection); + self::assertArrayHasKey('fields', $queryTypeIntrospection); unset($queryTypeIntrospection['fields']); - $this->expectExceptionMessageRegExp( + self::expectExceptionMessageRegExp( '/Introspection result missing fields: {"kind":"OBJECT","name":"Query",.*}\./' ); BuildClientSchema::build($introspection); @@ -789,22 +796,24 @@ public function testThrowsWhenMissingFields(): void /** * it('throws when missing field args', () => { */ - public function testThrowsWhenMissingFieldArgs(): void + public function testThrowsWhenMissingFieldArgs() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $queryTypeIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'Query'){ - $queryTypeIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; } + + $queryTypeIntrospection = &$type; } $firstField = &$queryTypeIntrospection['fields'][0]; - $this->assertArrayHasKey('args', $firstField); + self::assertArrayHasKey('args', $firstField); unset($firstField['args']); - $this->expectExceptionMessageRegExp( + self::expectExceptionMessageRegExp( '/Introspection result missing field args: {"name":"foo",.*}\./' ); BuildClientSchema::build($introspection); @@ -813,22 +822,24 @@ public function testThrowsWhenMissingFieldArgs(): void /** * it('throws when output type is used as an arg type', () => { */ - public function testThrowsWhenOutputTypeIsUsedAsAnArgType(): void + public function testThrowsWhenOutputTypeIsUsedAsAnArgType() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $queryTypeIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'Query'){ - $queryTypeIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; } + + $queryTypeIntrospection = &$type; } $firstArgType = &$queryTypeIntrospection['fields'][0]['args'][0]['type']; - $this->assertArrayHasKey('name', $firstArgType); + self::assertArrayHasKey('name', $firstArgType); $firstArgType['name'] = 'SomeUnion'; - $this->expectExceptionMessage( + self::expectExceptionMessage( 'Introspection must provide input type for arguments, but received: "SomeUnion".' ); BuildClientSchema::build($introspection); @@ -837,22 +848,24 @@ public function testThrowsWhenOutputTypeIsUsedAsAnArgType(): void /** * it('throws when input type is used as a field type', () => { */ - public function testThrowsWhenInputTypeIsUsedAsAFieldType(): void + public function testThrowsWhenInputTypeIsUsedAsAFieldType() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $queryTypeIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'Query'){ - $queryTypeIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; } + + $queryTypeIntrospection = &$type; } $firstFieldType = &$queryTypeIntrospection['fields'][0]['type']; - $this->assertArrayHasKey('name', $firstFieldType); + self::assertArrayHasKey('name', $firstFieldType); $firstFieldType['name'] = 'SomeInputObject'; - $this->expectExceptionMessage( + self::expectExceptionMessage( 'Introspection must provide output type for fields, but received: "SomeInputObject".' ); BuildClientSchema::build($introspection); @@ -861,21 +874,23 @@ public function testThrowsWhenInputTypeIsUsedAsAFieldType(): void /** * it('throws when missing possibleTypes', () => { */ - public function testThrowsWhenMissingPossibleTypes(): void + public function testThrowsWhenMissingPossibleTypes() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $someUnionIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'SomeUnion'){ - $someUnionIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'SomeUnion') { + continue; } + + $someUnionIntrospection = &$type; } - $this->assertArrayHasKey('possibleTypes', $someUnionIntrospection); + self::assertArrayHasKey('possibleTypes', $someUnionIntrospection); unset($someUnionIntrospection['possibleTypes']); - $this->expectExceptionMessageRegExp( + self::expectExceptionMessageRegExp( '/Introspection result missing possibleTypes: {"kind":"UNION","name":"SomeUnion",.*}\./' ); BuildClientSchema::build($introspection); @@ -884,21 +899,23 @@ public function testThrowsWhenMissingPossibleTypes(): void /** * it('throws when missing enumValues', () => { */ - public function testThrowsWhenMissingEnumValues(): void + public function testThrowsWhenMissingEnumValues() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $someEnumIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'SomeEnum'){ - $someEnumIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'SomeEnum') { + continue; } + + $someEnumIntrospection = &$type; } - $this->assertArrayHasKey('enumValues', $someEnumIntrospection); + self::assertArrayHasKey('enumValues', $someEnumIntrospection); unset($someEnumIntrospection['enumValues']); - $this->expectExceptionMessageRegExp( + self::expectExceptionMessageRegExp( '/Introspection result missing enumValues: {"kind":"ENUM","name":"SomeEnum",.*}\./' ); BuildClientSchema::build($introspection); @@ -907,21 +924,23 @@ public function testThrowsWhenMissingEnumValues(): void /** * it('throws when missing inputFields', () => { */ - public function testThrowsWhenMissingInputFields(): void + public function testThrowsWhenMissingInputFields() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $someInputObjectIntrospection = null; - foreach($introspection['__schema']['types'] as &$type) { - if($type['name'] === 'SomeInputObject'){ - $someInputObjectIntrospection = &$type; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'SomeInputObject') { + continue; } + + $someInputObjectIntrospection = &$type; } - $this->assertArrayHasKey('inputFields', $someInputObjectIntrospection); + self::assertArrayHasKey('inputFields', $someInputObjectIntrospection); unset($someInputObjectIntrospection['inputFields']); - $this->expectExceptionMessageRegExp( + self::expectExceptionMessageRegExp( '/Introspection result missing inputFields: {"kind":"INPUT_OBJECT","name":"SomeInputObject",.*}\./' ); BuildClientSchema::build($introspection); @@ -930,17 +949,17 @@ public function testThrowsWhenMissingInputFields(): void /** * it('throws when missing directive locations', () => { */ - public function testThrowsWhenMissingDirectiveLocations(): void + public function testThrowsWhenMissingDirectiveLocations() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $someDirectiveIntrospection = &$introspection['__schema']['directives'][0]; - $this->assertSame('SomeDirective', $someDirectiveIntrospection['name']); - $this->assertSame(['QUERY'], $someDirectiveIntrospection['locations']); + self::assertSame('SomeDirective', $someDirectiveIntrospection['name']); + self::assertSame(['QUERY'], $someDirectiveIntrospection['locations']); unset($someDirectiveIntrospection['locations']); - $this->expectExceptionMessageRegExp( + self::expectExceptionMessageRegExp( '/Introspection result missing directive locations: {"name":"SomeDirective",.*}\./' ); BuildClientSchema::build($introspection); @@ -949,17 +968,17 @@ public function testThrowsWhenMissingDirectiveLocations(): void /** * it('throws when missing directive args', () => { */ - public function testThrowsWhenMissingDirectiveArgs(): void + public function testThrowsWhenMissingDirectiveArgs() : void { - $introspection = Introspection::fromSchema($this->dummySchema()); + $introspection = Introspection::fromSchema(self::dummySchema()); $someDirectiveIntrospection = &$introspection['__schema']['directives'][0]; - $this->assertSame('SomeDirective', $someDirectiveIntrospection['name']); - $this->assertSame([], $someDirectiveIntrospection['args']); + self::assertSame('SomeDirective', $someDirectiveIntrospection['name']); + self::assertSame([], $someDirectiveIntrospection['args']); unset($someDirectiveIntrospection['args']); - $this->expectExceptionMessageRegExp( + self::expectExceptionMessageRegExp( '/Introspection result missing directive args: {"name":"SomeDirective",.*}\./' ); BuildClientSchema::build($introspection); @@ -970,16 +989,16 @@ public function testThrowsWhenMissingDirectiveArgs(): void /** * it('fails on very deep (> 7 levels) lists', () => { */ - public function testFailsOnVeryDeepListsWithMoreThan7Levels(): void + public function testFailsOnVeryDeepListsWithMoreThan7Levels() : void { - $schema = BuildSchema::build(' + $schema = BuildSchema::build(' type Query { foo: [[[[[[[[String]]]]]]]] } '); $introspection = Introspection::fromSchema($schema); - $this->expectExceptionMessage( + self::expectExceptionMessage( 'Decorated type deeper than introspection query.' ); BuildClientSchema::build($introspection); @@ -988,16 +1007,16 @@ public function testFailsOnVeryDeepListsWithMoreThan7Levels(): void /** * it('fails on very deep (> 7 levels) non-null', () => { */ - public function testFailsOnVeryDeepNonNullWithMoreThan7Levels(): void + public function testFailsOnVeryDeepNonNullWithMoreThan7Levels() : void { - $schema = BuildSchema::build(' + $schema = BuildSchema::build(' type Query { foo: [[[[String!]!]!]!] } '); $introspection = Introspection::fromSchema($schema); - $this->expectExceptionMessage( + self::expectExceptionMessage( 'Decorated type deeper than introspection query.' ); BuildClientSchema::build($introspection); @@ -1006,10 +1025,10 @@ public function testFailsOnVeryDeepNonNullWithMoreThan7Levels(): void /** * it('succeeds on deep (<= 7 levels) types', () => { */ - public function testSucceedsOnDeepTypesWithMoreThanOrEqualTo7Levels(): void + public function testSucceedsOnDeepTypesWithMoreThanOrEqualTo7Levels() : void { // e.g., fully non-null 3D matrix - $this->assertCycleIntrospection(' + self::assertCycleIntrospection(' type Query { foo: [[[String!]!]!]! } @@ -1021,9 +1040,9 @@ public function testSucceedsOnDeepTypesWithMoreThanOrEqualTo7Levels(): void /** * it('recursive interfaces', () => { */ - public function testRecursiveInterfaces(): void + public function testRecursiveInterfaces() : void { - $sdl = ' + $sdl = ' type Query { foo: Foo } @@ -1032,29 +1051,29 @@ public function testRecursiveInterfaces(): void foo: String } '; - $schema = BuildSchema::build($sdl); + $schema = BuildSchema::build($sdl); $introspection = Introspection::fromSchema($schema); - $this->expectExceptionMessage('Expected Foo to be a GraphQL Interface type.'); + self::expectExceptionMessage('Expected Foo to be a GraphQL Interface type.'); BuildClientSchema::build($introspection); } /** * it('recursive union', () => { */ - public function testRecursiveUnion(): void + public function testRecursiveUnion() : void { - $sdl = ' + $sdl = ' type Query { foo: Foo } union Foo = Foo '; - $schema = BuildSchema::build($sdl); + $schema = BuildSchema::build($sdl); $introspection = Introspection::fromSchema($schema); - $this->expectExceptionMessage('Expected Foo to be a GraphQL Object type.'); + self::expectExceptionMessage('Expected Foo to be a GraphQL Object type.'); BuildClientSchema::build($introspection); } } From 9603ef12e4a138b7b250acf18a34f83e6f9eaa13 Mon Sep 17 00:00:00 2001 From: spawnia Date: Tue, 19 Nov 2019 19:58:09 +0100 Subject: [PATCH 18/28] Change wording --- src/Type/Introspection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 334703cc8..bb412f661 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -186,12 +186,12 @@ public static function getTypes() } /** - * Build an IntrospectionQuery from a GraphQLSchema + * Build an introspection query from a Schema * - * IntrospectionQuery is useful for utilities that care about type and field + * Introspection is useful for utilities that care about type and field * relationships, but do not need to traverse through those relationships. * - * This is the inverse of BuildClientSchema. The primary use case is outside + * This is the inverse of BuildClientSchema::build(). The primary use case is outside * of the server context, for instance when doing schema comparisons. * * Options: From a15a2a847a3da7d6475d8ed0bafb43f19892ccf5 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 20 Nov 2019 11:56:40 +0100 Subject: [PATCH 19/28] Revert implementation of defaultValueExists --- src/Type/Definition/FieldArgument.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index e9ff86ba2..68d74fa7c 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -32,13 +32,6 @@ class FieldArgument /** @var InputType&Type */ private $type; - /** - * Helps to differentiate when `defaultValue` is `null` and when it was not even set initially - * - * @var bool - */ - private $defaultValueExists = false; - /** * @param mixed[] $def */ @@ -54,7 +47,6 @@ public function __construct(array $def) break; case 'defaultValue': $this->defaultValue = $value; - $this->defaultValueExists = true; break; case 'description': $this->description = $value; @@ -95,7 +87,7 @@ public function getType() : Type public function defaultValueExists() : bool { - return $this->defaultValueExists; + return array_key_exists('defaultValue', $this->config); } public function assertValid(FieldDefinition $parentField, Type $parentType) From 71550b0925f41c67f05d5fa8159d4004cac9170b Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 20 Nov 2019 13:03:40 +0100 Subject: [PATCH 20/28] Fix codestyle --- src/Type/Definition/FieldArgument.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Definition/FieldArgument.php b/src/Type/Definition/FieldArgument.php index 68d74fa7c..895504c94 100644 --- a/src/Type/Definition/FieldArgument.php +++ b/src/Type/Definition/FieldArgument.php @@ -46,7 +46,7 @@ public function __construct(array $def) $this->name = $value; break; case 'defaultValue': - $this->defaultValue = $value; + $this->defaultValue = $value; break; case 'description': $this->description = $value; From 622e632c4fd10675434e45a79549828f9e750e02 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 20 Nov 2019 13:04:06 +0100 Subject: [PATCH 21/28] Call instance method not statically --- tests/Utils/BuildClientSchemaTest.php | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index ac0870b3f..2ec04ee8e 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -625,7 +625,7 @@ enum SomeEnum { FOO } */ public function testThrowsWhenIntrospectionIsMissing__schemaProperty() : void { - self::expectExceptionMessage( + $this->expectExceptionMessage( 'Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: [].' ); BuildClientSchema::build([]); @@ -645,7 +645,7 @@ static function (array $type) : bool { } ); - self::expectExceptionMessage( + $this->expectExceptionMessage( 'Invalid or incomplete schema, unknown type: Query. Ensure that a full introspection query is used in order to build a client schema.' ); BuildClientSchema::build($introspection); @@ -670,7 +670,7 @@ static function (array $type) : bool { } ); - self::expectExceptionMessage( + $this->expectExceptionMessage( 'Invalid or incomplete schema, unknown type: Float. Ensure that a full introspection query is used in order to build a client schema.' ); BuildClientSchema::build($introspection); @@ -687,7 +687,7 @@ public function testThrowsWhenTypeReferenceIsMissingName() : void unset($introspection['__schema']['queryType']['name']); - self::expectExceptionMessage('Unknown type reference: [].'); + $this->expectExceptionMessage('Unknown type reference: [].'); BuildClientSchema::build($introspection); } @@ -710,7 +710,7 @@ public function testThrowsWhenMissingKind() : void unset($queryTypeIntrospection['kind']); - self::expectExceptionMessageRegExp( + $this->expectExceptionMessageRegExp( '/Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: {"name":"Query",.*}\./' ); BuildClientSchema::build($introspection); @@ -735,7 +735,7 @@ public function testThrowsWhenMissingInterfaces() : void unset($queryTypeIntrospection['interfaces']); - self::expectExceptionMessageRegExp( + $this->expectExceptionMessageRegExp( '/Introspection result missing interfaces: {"kind":"OBJECT","name":"Query",.*}\./' ); BuildClientSchema::build($introspection); @@ -787,7 +787,7 @@ public function testThrowsWhenMissingFields() : void unset($queryTypeIntrospection['fields']); - self::expectExceptionMessageRegExp( + $this->expectExceptionMessageRegExp( '/Introspection result missing fields: {"kind":"OBJECT","name":"Query",.*}\./' ); BuildClientSchema::build($introspection); @@ -813,7 +813,7 @@ public function testThrowsWhenMissingFieldArgs() : void unset($firstField['args']); - self::expectExceptionMessageRegExp( + $this->expectExceptionMessageRegExp( '/Introspection result missing field args: {"name":"foo",.*}\./' ); BuildClientSchema::build($introspection); @@ -839,7 +839,7 @@ public function testThrowsWhenOutputTypeIsUsedAsAnArgType() : void $firstArgType['name'] = 'SomeUnion'; - self::expectExceptionMessage( + $this->expectExceptionMessage( 'Introspection must provide input type for arguments, but received: "SomeUnion".' ); BuildClientSchema::build($introspection); @@ -865,7 +865,7 @@ public function testThrowsWhenInputTypeIsUsedAsAFieldType() : void $firstFieldType['name'] = 'SomeInputObject'; - self::expectExceptionMessage( + $this->expectExceptionMessage( 'Introspection must provide output type for fields, but received: "SomeInputObject".' ); BuildClientSchema::build($introspection); @@ -890,7 +890,7 @@ public function testThrowsWhenMissingPossibleTypes() : void unset($someUnionIntrospection['possibleTypes']); - self::expectExceptionMessageRegExp( + $this->expectExceptionMessageRegExp( '/Introspection result missing possibleTypes: {"kind":"UNION","name":"SomeUnion",.*}\./' ); BuildClientSchema::build($introspection); @@ -915,7 +915,7 @@ public function testThrowsWhenMissingEnumValues() : void unset($someEnumIntrospection['enumValues']); - self::expectExceptionMessageRegExp( + $this->expectExceptionMessageRegExp( '/Introspection result missing enumValues: {"kind":"ENUM","name":"SomeEnum",.*}\./' ); BuildClientSchema::build($introspection); @@ -940,7 +940,7 @@ public function testThrowsWhenMissingInputFields() : void unset($someInputObjectIntrospection['inputFields']); - self::expectExceptionMessageRegExp( + $this->expectExceptionMessageRegExp( '/Introspection result missing inputFields: {"kind":"INPUT_OBJECT","name":"SomeInputObject",.*}\./' ); BuildClientSchema::build($introspection); @@ -959,7 +959,7 @@ public function testThrowsWhenMissingDirectiveLocations() : void unset($someDirectiveIntrospection['locations']); - self::expectExceptionMessageRegExp( + $this->expectExceptionMessageRegExp( '/Introspection result missing directive locations: {"name":"SomeDirective",.*}\./' ); BuildClientSchema::build($introspection); @@ -978,7 +978,7 @@ public function testThrowsWhenMissingDirectiveArgs() : void unset($someDirectiveIntrospection['args']); - self::expectExceptionMessageRegExp( + $this->expectExceptionMessageRegExp( '/Introspection result missing directive args: {"name":"SomeDirective",.*}\./' ); BuildClientSchema::build($introspection); @@ -998,7 +998,7 @@ public function testFailsOnVeryDeepListsWithMoreThan7Levels() : void '); $introspection = Introspection::fromSchema($schema); - self::expectExceptionMessage( + $this->expectExceptionMessage( 'Decorated type deeper than introspection query.' ); BuildClientSchema::build($introspection); @@ -1016,7 +1016,7 @@ public function testFailsOnVeryDeepNonNullWithMoreThan7Levels() : void '); $introspection = Introspection::fromSchema($schema); - self::expectExceptionMessage( + $this->expectExceptionMessage( 'Decorated type deeper than introspection query.' ); BuildClientSchema::build($introspection); @@ -1054,7 +1054,7 @@ public function testRecursiveInterfaces() : void $schema = BuildSchema::build($sdl); $introspection = Introspection::fromSchema($schema); - self::expectExceptionMessage('Expected Foo to be a GraphQL Interface type.'); + $this->expectExceptionMessage('Expected Foo to be a GraphQL Interface type.'); BuildClientSchema::build($introspection); } @@ -1073,7 +1073,7 @@ public function testRecursiveUnion() : void $schema = BuildSchema::build($sdl); $introspection = Introspection::fromSchema($schema); - self::expectExceptionMessage('Expected Foo to be a GraphQL Object type.'); + $this->expectExceptionMessage('Expected Foo to be a GraphQL Object type.'); BuildClientSchema::build($introspection); } } From 063c0d5342e7704be9add1ade6ce0859af25902c Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 20 Nov 2019 13:11:43 +0100 Subject: [PATCH 22/28] Fix codestyle --- tests/Utils/BuildClientSchemaTest.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 2ec04ee8e..b8c497ed9 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -597,6 +597,11 @@ public function testUseClientSchemaForLimitedExecution() : void // describe('throws when given invalid introspection', () => { + /** + * Construct a default dummy schema that is used in the following tests. + * + * @return \GraphQL\Type\Schema + */ protected static function dummySchema() : Schema { return BuildSchema::build(' @@ -623,7 +628,7 @@ enum SomeEnum { FOO } /** * it('throws when introspection is missing __schema property', () => { */ - public function testThrowsWhenIntrospectionIsMissing__schemaProperty() : void + public function testThrowsWhenIntrospectionIsMissingSchemaProperty() : void { $this->expectExceptionMessage( 'Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: [].' From c4d439639c211bb05b16b40da70d3af8ddb0cb16 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 20 Nov 2019 13:58:25 +0100 Subject: [PATCH 23/28] Make callable functions public --- src/Utils/BuildClientSchema.php | 2 +- tests/Utils/BuildClientSchemaTest.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index f7c4c5e1e..c6542700d 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -235,7 +235,7 @@ private function getObjectType(array $typeRef) : ObjectType /** * @param array $typeRef */ - private function getInterfaceType(array $typeRef) : InterfaceType + public function getInterfaceType(array $typeRef) : InterfaceType { $type = $this->getType($typeRef); diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index b8c497ed9..77420a44a 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -599,8 +599,6 @@ public function testUseClientSchemaForLimitedExecution() : void /** * Construct a default dummy schema that is used in the following tests. - * - * @return \GraphQL\Type\Schema */ protected static function dummySchema() : Schema { From 44e97186fa720188dceb92da459481201a8350d3 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 20 Jan 2020 00:09:16 +0100 Subject: [PATCH 24/28] Update src/Type/Introspection.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Šimon Podlipský --- src/Type/Introspection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index bb412f661..63229c10e 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -201,7 +201,7 @@ public static function getTypes() * * @param array $options * - * @return array|null + * @return array>|null */ public static function fromSchema(Schema $schema, array $options = []) : ?array { From 7525bebe8aefd31fa4818dc09b364a5c3d6e7857 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 20 Jan 2020 00:16:02 +0100 Subject: [PATCH 25/28] Update tests/Utils/BuildClientSchemaTest.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Šimon Podlipský --- tests/Utils/BuildClientSchemaTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 77420a44a..814f69dd7 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -32,7 +32,7 @@ protected static function assertCycleIntrospection(string $sdl) : void } /** - * @return array + * @return array> */ protected static function introspectionFromSDL(string $sdl) : array { From 4203e0da18b2f15606b97db5d316ed22461fb6fd Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 20 Jan 2020 13:15:50 +0100 Subject: [PATCH 26/28] Iterable --- src/Utils/Utils.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 5e7ce6bac..e6b7d2f10 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -272,9 +272,9 @@ public static function groupBy($traversable, callable $keyFn) } /** - * @param array|Traversable $traversable + * @param iterable $traversable * - * @return array + * @return iterable */ public static function keyValMap($traversable, callable $keyFn, callable $valFn) { From d78980b2f0addf70b139852c7b0de43657444dc6 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 20 Jan 2020 13:31:41 +0100 Subject: [PATCH 27/28] Revert change in Utils.php --- src/Utils/Utils.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index e6b7d2f10..695591bb6 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -272,9 +272,9 @@ public static function groupBy($traversable, callable $keyFn) } /** - * @param iterable $traversable + * @param mixed[]|Traversable $traversable * - * @return iterable + * @return mixed[][] */ public static function keyValMap($traversable, callable $keyFn, callable $valFn) { From c35106a131a8ab4d1912c1ab1418b8930ab7a9ca Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 20 Jan 2020 13:37:59 +0100 Subject: [PATCH 28/28] Re-add minimal docs fix --- src/Utils/Utils.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 695591bb6..863c3ab62 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -272,9 +272,9 @@ public static function groupBy($traversable, callable $keyFn) } /** - * @param mixed[]|Traversable $traversable + * @param iterable $traversable * - * @return mixed[][] + * @return array */ public static function keyValMap($traversable, callable $keyFn, callable $valFn) {