From 3530af2efe9a53a40fe1effacbcae387d9fc9066 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Fri, 15 Oct 2021 09:37:03 +0200 Subject: [PATCH 1/6] Exclude unused standard types from the schema https://github.com/webonyx/graphql-php/issues/964 --- src/Type/Introspection.php | 31 +- src/Type/Schema.php | 5 +- tests/Executor/ExecutorLazySchemaTest.php | 60 +- tests/StarWarsIntrospectionTest.php | 5 +- tests/Type/IntrospectionTest.php | 1816 ++++++++++----------- tests/Utils/BreakingChangesFinderTest.php | 10 +- tests/Utils/BuildClientSchemaTest.php | 1 - tests/Validator/KnownTypeNamesTest.php | 2 +- 8 files changed, 874 insertions(+), 1056 deletions(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index ed2c105ed..93a5c2d50 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -10,6 +10,7 @@ use GraphQL\Language\Printer; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\EnumType; +use GraphQL\Type\Definition\EnumValueDefinition; use GraphQL\Type\Definition\FieldArgument; use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InputObjectField; @@ -329,7 +330,10 @@ public static function _type(): ObjectType 'fields' => [ 'type' => Type::listOf(Type::nonNull(self::_field())), 'args' => [ - 'includeDeprecated' => ['type' => Type::boolean(), 'defaultValue' => false], + 'includeDeprecated' => [ + 'type' => Type::boolean(), + 'defaultValue' => false, + ], ], 'resolve' => static function (Type $type, $args): ?array { if ($type instanceof ObjectType || $type instanceof InterfaceType) { @@ -339,7 +343,8 @@ public static function _type(): ObjectType $fields = array_filter( $fields, static function (FieldDefinition $field): bool { - return ($field->deprecationReason ?? '') === ''; + return $field->deprecationReason === null + || $field->deprecationReason === ''; } ); } @@ -377,13 +382,14 @@ static function (FieldDefinition $field): bool { ], 'resolve' => static function ($type, $args): ?array { if ($type instanceof EnumType) { - $values = array_values($type->getValues()); + $values = $type->getValues(); if (! ($args['includeDeprecated'] ?? false)) { - $values = array_filter( + return array_filter( $values, - static function ($value): bool { - return ($value->deprecationReason ?? '') === ''; + static function (EnumValueDefinition $value): bool { + return $value->deprecationReason === null + || $value->deprecationReason === ''; } ); } @@ -499,7 +505,8 @@ public static function _field(): ObjectType 'isDeprecated' => [ 'type' => Type::nonNull(Type::boolean()), 'resolve' => static function (FieldDefinition $field): bool { - return (bool) $field->deprecationReason; + return $field->deprecationReason !== null + && $field->deprecationReason !== ''; }, ], 'deprecationReason' => [ @@ -595,14 +602,15 @@ public static function _enumValue(): ObjectType ], 'isDeprecated' => [ 'type' => Type::nonNull(Type::boolean()), - 'resolve' => static function ($enumValue): bool { - return (bool) $enumValue->deprecationReason; + 'resolve' => static function (EnumValueDefinition $value): bool { + return $value->deprecationReason !== null + && $value->deprecationReason !== ''; }, ], 'deprecationReason' => [ 'type' => Type::string(), - 'resolve' => static function ($enumValue) { - return $enumValue->deprecationReason; + 'resolve' => static function (EnumValueDefinition $enumValue): ?string { + return $enumValue->deprecationReason; }, ], ], @@ -742,7 +750,6 @@ public static function _directiveLocation(): EnumType 'value' => DirectiveLocation::INPUT_FIELD_DEFINITION, 'description' => 'Location adjacent to an input object field definition.', ], - ], ]); } diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 9cbfee221..07db4f7c8 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -135,7 +135,7 @@ public function __construct($config) ); } - $this->resolvedTypes += Type::getStandardTypes() + Introspection::getTypes(); + $this->resolvedTypes += Introspection::getTypes(); if (isset($this->config->typeLoader)) { return; @@ -296,7 +296,8 @@ public function getConfig(): SchemaConfig public function getType(string $name): ?Type { if (! isset($this->resolvedTypes[$name])) { - $type = $this->loadType($name); + $type = Type::getStandardTypes()[$name] + ?? $this->loadType($name); if ($type === null) { return null; diff --git a/tests/Executor/ExecutorLazySchemaTest.php b/tests/Executor/ExecutorLazySchemaTest.php index acdea9ea7..2c8d0f7f8 100644 --- a/tests/Executor/ExecutorLazySchemaTest.php +++ b/tests/Executor/ExecutorLazySchemaTest.php @@ -28,38 +28,29 @@ class ExecutorLazySchemaTest extends TestCase { - /** @var ScalarType */ - public $someScalarType; + public ScalarType $someScalarType; - /** @var ObjectType */ - public $someObjectType; + public ObjectType $someObjectType; - /** @var ObjectType */ - public $otherObjectType; + public ObjectType $otherObjectType; - /** @var ObjectType */ - public $deeperObjectType; + public ObjectType $deeperObjectType; - /** @var UnionType */ - public $someUnionType; + public UnionType $someUnionType; - /** @var InterfaceType */ - public $someInterfaceType; + public InterfaceType $someInterfaceType; - /** @var EnumType */ - public $someEnumType; + public EnumType $someEnumType; - /** @var InputObjectType */ - public $someInputObjectType; + public InputObjectType $someInputObjectType; - /** @var ObjectType */ - public $queryType; + public ObjectType $queryType; - /** @var string[] */ - public $calls = []; + /** @var array */ + public array $calls = []; - /** @var bool[] */ - public $loadedTypes = []; + /** @var array */ + public array $loadedTypes = []; public function testWarnsAboutSlowIsTypeOfForLazySchema(): void { @@ -372,36 +363,31 @@ public function testDeepQuery(): void { $schema = new Schema([ 'query' => $this->loadType('Query'), - 'typeLoader' => function ($name) { + 'typeLoader' => function (string $name): Type { return $this->loadType($name, true); }, ]); - $query = '{ object { object { object { string } } } }'; + $query = '{ object { object { object { string } } } }'; + $rootValue = ['object' => ['object' => ['object' => ['string' => 'test']]]]; + $result = Executor::execute( $schema, Parser::parse($query), - ['object' => ['object' => ['object' => ['string' => 'test']]]] + $rootValue ); - $expected = [ - 'data' => ['object' => ['object' => ['object' => ['string' => 'test']]]], - ]; - $expectedLoadedTypes = [ + self::assertEquals(['data' => $rootValue], $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); + self::assertEquals([ 'Query' => true, 'SomeObject' => true, 'OtherObject' => true, - ]; - - self::assertEquals($expected, $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); - self::assertEquals($expectedLoadedTypes, $this->loadedTypes); - - $expectedExecutorCalls = [ + ], $this->loadedTypes); + self::assertEquals([ 'Query.fields', 'SomeObject', 'SomeObject.fields', - ]; - self::assertEquals($expectedExecutorCalls, $this->calls); + ], $this->calls); } public function testResolveUnion(): void diff --git a/tests/StarWarsIntrospectionTest.php b/tests/StarWarsIntrospectionTest.php index 312f8a3db..95b695aee 100644 --- a/tests/StarWarsIntrospectionTest.php +++ b/tests/StarWarsIntrospectionTest.php @@ -35,13 +35,10 @@ public function testAllowsQueryingTheSchemaForTypes(): void ['name' => 'String'], ['name' => 'Human'], ['name' => 'Droid'], - ['name' => 'ID'], - ['name' => 'Float'], - ['name' => 'Int'], - ['name' => 'Boolean'], ['name' => '__Schema'], ['name' => '__Type'], ['name' => '__TypeKind'], + ['name' => 'Boolean'], ['name' => '__Field'], ['name' => '__InputValue'], ['name' => '__EnumValue'], diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php index b13174ffe..0e347c889 100644 --- a/tests/Type/IntrospectionTest.php +++ b/tests/Type/IntrospectionTest.php @@ -32,1043 +32,869 @@ public function testExecutesAnIntrospectionQuery(): void $emptySchema = new Schema([ 'query' => new ObjectType([ 'name' => 'QueryRoot', - 'fields' => ['a' => Type::string()], + 'fields' => [ + 'a' => Type::string(), + ], ]), ]); - $request = Introspection::getIntrospectionQuery([ + $request = Introspection::getIntrospectionQuery([ 'descriptions' => false, 'directiveIsRepeatable' => true, ]); + $expected = [ - 'data' => - [ - '__schema' => + 'data' => [ + '__schema' => [ + 'mutationType' => null, + 'subscriptionType' => null, + 'queryType' => ['name' => 'QueryRoot'], + 'types' => [ [ - 'mutationType' => null, - 'subscriptionType' => null, - 'queryType' => - ['name' => 'QueryRoot'], - 'types' => + 'kind' => 'OBJECT', + 'name' => 'QueryRoot', + 'inputFields' => null, + 'interfaces' => [], + 'enumValues' => null, + 'possibleTypes' => null, + 'fields' => [ [ - [ - 'kind' => 'OBJECT', - 'name' => 'QueryRoot', - 'inputFields' => null, - 'interfaces' => - [], - 'enumValues' => null, - 'possibleTypes' => null, - 'fields' => [ - [ - 'name' => 'a', - 'args' => [], - 'type' => [ - 'kind' => 'SCALAR', - 'name' => 'String', + 'name' => 'a', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + ], + ], + [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'fields' => null, + 'inputFields' => null, + 'interfaces' => null, + 'enumValues' => null, + 'possibleTypes' => null, + ], + [ + 'kind' => 'OBJECT', + 'name' => '__Schema', + 'fields' => [ + 0 => [ + 'name' => 'types', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__Type', 'ofType' => null, ], - 'isDeprecated' => false, - 'deprecationReason' => null, ], ], ], - [ - 'kind' => 'SCALAR', - 'name' => 'String', - 'fields' => null, - 'inputFields' => null, - 'interfaces' => null, - 'enumValues' => null, - 'possibleTypes' => null, + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 1 => [ + 'name' => 'queryType', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__Type', + 'ofType' => null, + ], ], - [ - 'kind' => 'SCALAR', - 'name' => 'ID', - 'fields' => null, - 'inputFields' => null, - 'interfaces' => null, - 'enumValues' => null, - 'possibleTypes' => null, + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'mutationType', + 'args' => [], + 'type' => [ + 'kind' => 'OBJECT', + 'name' => '__Type', + 'ofType' => null, ], - [ - 'kind' => 'SCALAR', - 'name' => 'Float', - 'fields' => null, - 'inputFields' => null, - 'interfaces' => null, - 'enumValues' => null, - 'possibleTypes' => null, + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'subscriptionType', + 'args' => [], + 'type' => [ + 'kind' => 'OBJECT', + 'name' => '__Type', + 'ofType' => null, ], - [ - 'kind' => 'SCALAR', - 'name' => 'Int', - 'fields' => null, - 'inputFields' => null, - 'interfaces' => null, - 'enumValues' => null, - 'possibleTypes' => null, + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'directives', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__Directive', + ], + ], + ], ], - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - 'fields' => null, - 'inputFields' => null, - 'interfaces' => null, - 'enumValues' => null, - 'possibleTypes' => null, + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + ], + 'inputFields' => null, + 'interfaces' => [], + 'enumValues' => null, + 'possibleTypes' => null, + ], + [ + 'kind' => 'OBJECT', + 'name' => '__Type', + 'fields' => [ + 0 => [ + 'name' => 'kind', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'ENUM', + 'name' => '__TypeKind', + ], ], - [ - 'kind' => 'OBJECT', - 'name' => '__Schema', - 'fields' => - [ - 0 => [ - 'name' => 'types', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - ], - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 1 => [ - 'name' => 'queryType', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'mutationType', - 'args' => - [], - 'type' => - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'subscriptionType', - 'args' => - [], - 'type' => - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'directives', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__Directive', - ], - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 1 => [ + 'name' => 'name', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 2 => [ + 'name' => 'description', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 3 => [ + 'name' => 'fields', + 'args' => [ + 0 => [ + 'name' => 'includeDeprecated', + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'Boolean', ], - 'inputFields' => null, - 'interfaces' => - [], - 'enumValues' => null, - 'possibleTypes' => null, + 'defaultValue' => 'false', + ], ], - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - 'fields' => - [ - 0 => - [ - 'name' => 'kind', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'ENUM', - 'name' => '__TypeKind', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 1 => - [ - 'name' => 'name', - 'args' => - [], - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 2 => - [ - 'name' => 'description', - 'args' => - [], - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 3 => - [ - 'name' => 'fields', - 'args' => - [ - 0 => - [ - 'name' => 'includeDeprecated', - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - ], - 'defaultValue' => 'false', - ], - ], - 'type' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__Field', - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 4 => - [ - 'name' => 'interfaces', - 'args' => - [], - 'type' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 5 => - [ - 'name' => 'possibleTypes', - 'args' => - [], - 'type' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 6 => - [ - 'name' => 'enumValues', - 'args' => - [ - 0 => - [ - 'name' => 'includeDeprecated', - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - ], - 'defaultValue' => 'false', - ], - ], - 'type' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__EnumValue', - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 7 => - [ - 'name' => 'inputFields', - 'args' => - [], - 'type' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__InputValue', - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 8 => - [ - 'name' => 'ofType', - 'args' => - [], - 'type' => - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], + 'type' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__Field', ], - 'inputFields' => null, - 'interfaces' => - [], - 'enumValues' => null, - 'possibleTypes' => null, + ], ], - [ - 'kind' => 'ENUM', - 'name' => '__TypeKind', - 'fields' => null, - 'inputFields' => null, - 'interfaces' => null, - 'enumValues' => - [ - 0 => - [ - 'name' => 'SCALAR', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 1 => - [ - 'name' => 'OBJECT', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 2 => - [ - 'name' => 'INTERFACE', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 3 => - [ - 'name' => 'UNION', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 4 => - [ - 'name' => 'ENUM', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 5 => - [ - 'name' => 'INPUT_OBJECT', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 6 => - [ - 'name' => 'LIST', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 7 => - [ - 'name' => 'NON_NULL', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 4 => [ + 'name' => 'interfaces', + 'args' => [], + 'type' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__Type', + 'ofType' => null, ], - 'possibleTypes' => null, + ], ], - [ - 'kind' => 'OBJECT', - 'name' => '__Field', - 'fields' => - [ - 0 => - [ - 'name' => 'name', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 1 => - [ - 'name' => 'description', - 'args' => - [], - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 2 => - [ - 'name' => 'args', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__InputValue', - ], - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 3 => - [ - 'name' => 'type', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 4 => - [ - 'name' => 'isDeprecated', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 5 => - [ - 'name' => 'deprecationReason', - 'args' => - [], - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 5 => [ + 'name' => 'possibleTypes', + 'args' => [], + 'type' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__Type', + 'ofType' => null, ], - 'inputFields' => null, - 'interfaces' => - [], - 'enumValues' => null, - 'possibleTypes' => null, + ], ], - [ - 'kind' => 'OBJECT', - 'name' => '__InputValue', - 'fields' => - [ - 0 => - [ - 'name' => 'name', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 1 => - [ - 'name' => 'description', - 'args' => - [], - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 2 => - [ - 'name' => 'type', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__Type', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 3 => - [ - 'name' => 'defaultValue', - 'args' => - [], - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 6 => [ + 'name' => 'enumValues', + 'args' => [ + 0 => [ + 'name' => 'includeDeprecated', + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'Boolean', ], - 'inputFields' => null, - 'interfaces' => - [], - 'enumValues' => null, - 'possibleTypes' => null, + 'defaultValue' => 'false', + ], ], - [ - 'kind' => 'OBJECT', - 'name' => '__EnumValue', - 'fields' => - [ - 0 => - [ - 'name' => 'name', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 1 => - [ - 'name' => 'description', - 'args' => - [], - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 2 => - [ - 'name' => 'isDeprecated', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 3 => - [ - 'name' => 'deprecationReason', - 'args' => - [], - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], + 'type' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__EnumValue', ], - 'inputFields' => null, - 'interfaces' => - [], - 'enumValues' => null, - 'possibleTypes' => null, + ], ], - [ - 'kind' => 'OBJECT', - 'name' => '__Directive', - 'fields' => - [ - 0 => - [ - 'name' => 'name', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 1 => - [ - 'name' => 'description', - 'args' => - [], - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 2 => - [ - 'name' => 'args', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'OBJECT', - 'name' => '__InputValue', - ], - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 3 => - [ - 'name' => 'isRepeatable', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - 'ofType' => null, - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 4 => - [ - 'name' => 'locations', - 'args' => - [], - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'ENUM', - 'name' => '__DirectiveLocation', - ], - ], - ], - ], - 'isDeprecated' => false, - 'deprecationReason' => null, - ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 7 => [ + 'name' => 'inputFields', + 'args' => [], + 'type' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__InputValue', + 'ofType' => null, ], - 'inputFields' => null, - 'interfaces' => - [], - 'enumValues' => null, - 'possibleTypes' => null, + ], ], - [ - 'kind' => 'ENUM', - 'name' => '__DirectiveLocation', - 'fields' => null, - 'inputFields' => null, - 'interfaces' => null, - 'enumValues' => - [ - 0 => [ - 'name' => 'QUERY', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 1 => [ - 'name' => 'MUTATION', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 2 => [ - 'name' => 'SUBSCRIPTION', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 3 => [ - 'name' => 'FIELD', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 4 => [ - 'name' => 'FRAGMENT_DEFINITION', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 5 => [ - 'name' => 'FRAGMENT_SPREAD', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 6 => [ - 'name' => 'INLINE_FRAGMENT', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - 7 => [ - 'name' => 'VARIABLE_DEFINITION', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'SCHEMA', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'SCALAR', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'OBJECT', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'FIELD_DEFINITION', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'ARGUMENT_DEFINITION', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'INTERFACE', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'UNION', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'ENUM', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'ENUM_VALUE', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'INPUT_OBJECT', - 'isDeprecated' => false, - 'deprecationReason' => null, - ], - [ - 'name' => 'INPUT_FIELD_DEFINITION', - 'isDeprecated' => false, - 'deprecationReason' => null, + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 8 => [ + 'name' => 'ofType', + 'args' => [], + 'type' => [ + 'kind' => 'OBJECT', + 'name' => '__Type', + 'ofType' => null, + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + ], + 'inputFields' => null, + 'interfaces' => [], + 'enumValues' => null, + 'possibleTypes' => null, + ], + [ + 'kind' => 'ENUM', + 'name' => '__TypeKind', + 'fields' => null, + 'inputFields' => null, + 'interfaces' => null, + 'enumValues' => [ + 0 => [ + 'name' => 'SCALAR', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 1 => [ + 'name' => 'OBJECT', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 2 => [ + 'name' => 'INTERFACE', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 3 => [ + 'name' => 'UNION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 4 => [ + 'name' => 'ENUM', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 5 => [ + 'name' => 'INPUT_OBJECT', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 6 => [ + 'name' => 'LIST', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 7 => [ + 'name' => 'NON_NULL', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + ], + 'possibleTypes' => null, + ], + [ + 'kind' => 'SCALAR', + 'name' => 'Boolean', + 'fields' => null, + 'inputFields' => null, + 'interfaces' => null, + 'enumValues' => null, + 'possibleTypes' => null, + ], + [ + 'kind' => 'OBJECT', + 'name' => '__Field', + 'fields' => [ + 0 => [ + 'name' => 'name', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 1 => [ + 'name' => 'description', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 2 => [ + 'name' => 'args', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__InputValue', + 'ofType' => null, ], ], - 'possibleTypes' => null, + ], ], + 'isDeprecated' => false, + 'deprecationReason' => null, ], - 'directives' => - [ - 0 => - [ - 'name' => 'include', - 'isRepeatable' => false, - 'args' => - [ - 0 => - [ - 'defaultValue' => null, - 'name' => 'if', - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - ], - ], - ], - ], - 'locations' => - [ - 0 => 'FIELD', - 1 => 'FRAGMENT_SPREAD', - 2 => 'INLINE_FRAGMENT', - ], + 3 => [ + 'name' => 'type', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__Type', + 'ofType' => null, ], - 1 => - [ - 'name' => 'skip', - 'isRepeatable' => false, - 'args' => - [ - 0 => - [ - 'defaultValue' => null, - 'name' => 'if', - 'type' => - [ - 'kind' => 'NON_NULL', - 'name' => null, - 'ofType' => - [ - 'kind' => 'SCALAR', - 'name' => 'Boolean', - ], - ], - ], - ], - 'locations' => - [ - 0 => 'FIELD', - 1 => 'FRAGMENT_SPREAD', - 2 => 'INLINE_FRAGMENT', - ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 4 => [ + 'name' => 'isDeprecated', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'Boolean', + 'ofType' => null, + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 5 => [ + 'name' => 'deprecationReason', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + ], + 'inputFields' => null, + 'interfaces' => [], + 'enumValues' => null, + 'possibleTypes' => null, + ], + [ + 'kind' => 'OBJECT', + 'name' => '__InputValue', + 'fields' => [ + 0 => [ + 'name' => 'name', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 1 => [ + 'name' => 'description', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 2 => [ + 'name' => 'type', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__Type', + 'ofType' => null, + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 3 => [ + 'name' => 'defaultValue', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + ], + 'inputFields' => null, + 'interfaces' => [], + 'enumValues' => null, + 'possibleTypes' => null, + ], + [ + 'kind' => 'OBJECT', + 'name' => '__EnumValue', + 'fields' => [ + 0 => [ + 'name' => 'name', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, ], - 2 => - [ - 'name' => 'deprecated', - 'isRepeatable' => false, - 'args' => - [ - 0 => - [ - 'defaultValue' => '"No longer supported"', - 'name' => 'reason', - 'type' => - [ - 'kind' => 'SCALAR', - 'name' => 'String', - 'ofType' => null, - ], - ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 1 => [ + 'name' => 'description', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 2 => [ + 'name' => 'isDeprecated', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'Boolean', + 'ofType' => null, + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 3 => [ + 'name' => 'deprecationReason', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + ], + 'inputFields' => null, + 'interfaces' => [], + 'enumValues' => null, + 'possibleTypes' => null, + ], + [ + 'kind' => 'OBJECT', + 'name' => '__Directive', + 'fields' => [ + 0 => [ + 'name' => 'name', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 1 => [ + 'name' => 'description', + 'args' => [], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 2 => [ + 'name' => 'args', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'OBJECT', + 'name' => '__InputValue', + 'ofType' => null, ], - 'locations' => - [ - 0 => 'FIELD_DEFINITION', - 1 => 'ENUM_VALUE', + ], + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 3 => [ + 'name' => 'isRepeatable', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'Boolean', + 'ofType' => null, + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 4 => [ + 'name' => 'locations', + 'args' => [], + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'ENUM', + 'name' => '__DirectiveLocation', + 'ofType' => null, ], + ], + ], + ], + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + ], + 'inputFields' => null, + 'interfaces' => [], + 'enumValues' => null, + 'possibleTypes' => null, + ], + [ + 'kind' => 'ENUM', + 'name' => '__DirectiveLocation', + 'fields' => null, + 'inputFields' => null, + 'interfaces' => null, + 'enumValues' => [ + 0 => [ + 'name' => 'QUERY', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 1 => [ + 'name' => 'MUTATION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 2 => [ + 'name' => 'SUBSCRIPTION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 3 => [ + 'name' => 'FIELD', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 4 => [ + 'name' => 'FRAGMENT_DEFINITION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 5 => [ + 'name' => 'FRAGMENT_SPREAD', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 6 => [ + 'name' => 'INLINE_FRAGMENT', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + 7 => [ + 'name' => 'VARIABLE_DEFINITION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'SCHEMA', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'SCALAR', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'OBJECT', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'FIELD_DEFINITION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'ARGUMENT_DEFINITION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'INTERFACE', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'UNION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'ENUM', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'ENUM_VALUE', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'INPUT_OBJECT', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + [ + 'name' => 'INPUT_FIELD_DEFINITION', + 'isDeprecated' => false, + 'deprecationReason' => null, + ], + ], + 'possibleTypes' => null, + ], + ], + 'directives' => [ + 0 => [ + 'name' => 'include', + 'isRepeatable' => false, + 'args' => [ + 0 => [ + 'defaultValue' => null, + 'name' => 'if', + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'Boolean', + 'ofType' => null, ], + ], + ], + ], + 'locations' => [ + 0 => 'FIELD', + 1 => 'FRAGMENT_SPREAD', + 2 => 'INLINE_FRAGMENT', + ], + ], + 1 => [ + 'name' => 'skip', + 'isRepeatable' => false, + 'args' => [ + 0 => [ + 'defaultValue' => null, + 'name' => 'if', + 'type' => [ + 'kind' => 'NON_NULL', + 'name' => null, + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'Boolean', + 'ofType' => null, + ], + ], ], + ], + 'locations' => [ + 0 => 'FIELD', + 1 => 'FRAGMENT_SPREAD', + 2 => 'INLINE_FRAGMENT', + ], ], + 2 => [ + 'name' => 'deprecated', + 'isRepeatable' => false, + 'args' => [ + 0 => [ + 'defaultValue' => '"No longer supported"', + 'name' => 'reason', + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], + ], + ], + 'locations' => [ + 0 => 'FIELD_DEFINITION', + 1 => 'ENUM_VALUE', + ], + ], + ], ], + ], ]; $actual = GraphQL::executeQuery($emptySchema, $request)->toArray(); @@ -1154,7 +980,11 @@ public function testIntrospectsOnInputObject(): void 'type' => [ 'kind' => 'LIST', 'name' => null, - 'ofType' => ['kind' => 'SCALAR', 'name' => 'String', 'ofType' => null], + 'ofType' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null, + ], ], 'defaultValue' => null, ], diff --git a/tests/Utils/BreakingChangesFinderTest.php b/tests/Utils/BreakingChangesFinderTest.php index 750bfa978..01853b94e 100644 --- a/tests/Utils/BreakingChangesFinderTest.php +++ b/tests/Utils/BreakingChangesFinderTest.php @@ -1309,12 +1309,10 @@ public function testShouldDetectAllBreakingChanges(): void 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED, 'description' => 'TypeInUnion2 was removed.', ], - /* This is reported in the js version because builtin sclar types are added on demand - and not like here always - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, - 'description' => 'Int was removed.' - ],*/ + [ + 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED, + 'description' => 'Int was removed.', + ], [ 'type' => BreakingChangesFinder::BREAKING_CHANGE_TYPE_CHANGED_KIND, 'description' => 'TypeThatChangesType changed from an Object type to an Interface type.', diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 257fea2e5..6fd478a86 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -165,7 +165,6 @@ public function testUsesBuiltInScalarsWhenPossible(): void */ public function testIncludesStandardTypesOnlyIfTheyAreUsed(): void { - self::markTestSkipped('Introspection currently does not follow the reference implementation.'); $clientSchema = self::clientSchemaFromSDL(' type Query { foo: String diff --git a/tests/Validator/KnownTypeNamesTest.php b/tests/Validator/KnownTypeNamesTest.php index 02448aca6..c39d8fdce 100644 --- a/tests/Validator/KnownTypeNamesTest.php +++ b/tests/Validator/KnownTypeNamesTest.php @@ -68,7 +68,7 @@ public function testUnknownTypeNamesAreInvalid(): void */ public function testReferencesToStandardScalarsThatAreMissingInSchema(): void { - self::markTestSkipped('TODO https://github.com/webonyx/graphql-php/issues/964'); + self::markTestSkipped('Missing implementation for SDL validation for now'); $schema = BuildSchema::build('type Query { foo: String }'); $query = ' query ($id: ID, $float: Float, $int: Int) { From f82f9c04dce7fb35d578c20e1032d3ebd5bdeca5 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Fri, 15 Oct 2021 11:35:55 +0200 Subject: [PATCH 2/6] Fix implementation --- src/Type/Introspection.php | 4 ++ src/Utils/BuildClientSchema.php | 88 ++++++++++++----------- src/Utils/TypeInfo.php | 3 +- tests/Executor/ExecutorLazySchemaTest.php | 35 +++++---- tests/Utils/BuildClientSchemaTest.php | 46 +++++++++++- 5 files changed, 118 insertions(+), 58 deletions(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 93a5c2d50..5d277a470 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -33,6 +33,7 @@ use function array_merge; use function array_values; use function method_exists; +use function var_dump; class Introspection { @@ -213,6 +214,9 @@ public static function fromSchema(Schema $schema, array $options = []): ?array $schema, self::getIntrospectionQuery($optionsWithDefaults) ); + foreach ($result->errors as $error) { + var_dump($error->getMessage()); + } return $result->data; } diff --git a/src/Utils/BuildClientSchema.php b/src/Utils/BuildClientSchema.php index 2114b9c6e..b2f362625 100644 --- a/src/Utils/BuildClientSchema.php +++ b/src/Utils/BuildClientSchema.php @@ -55,7 +55,7 @@ class BuildClientSchema private array $options; /** @var array */ - private array $typeMap; + private array $typeMap = []; /** * @param array $introspectionQuery @@ -101,26 +101,21 @@ public function buildSchema(): Schema $schemaIntrospection = $this->introspection['__schema']; - $this->typeMap = Utils::keyValMap( - $schemaIntrospection['types'], - static function (array $typeIntrospection) { - return $typeIntrospection['name']; - }, - function (array $typeIntrospection): NamedType { - return $this->buildType($typeIntrospection); - } - ); - $builtInTypes = array_merge( Type::getStandardTypes(), Introspection::getTypes() ); - foreach ($builtInTypes as $name => $type) { - if (! isset($this->typeMap[$name])) { - continue; + + foreach ($schemaIntrospection['types'] as $typeIntrospection) { + if (! isset($typeIntrospection['name'])) { + throw self::invalidOrIncompleteIntrospectionResult($typeIntrospection); } - $this->typeMap[$name] = $type; + $name = $typeIntrospection['name']; + + // Use the built-in singleton types to avoid reconstruction + $this->typeMap[$name] = $builtInTypes[$name] + ?? $this->buildType($typeIntrospection); } $queryType = isset($schemaIntrospection['queryType']) @@ -142,17 +137,13 @@ function (array $typeIntrospection): NamedType { ) : []; - $schemaConfig = new SchemaConfig(); - $schemaConfig->setQuery($queryType) + $schemaConfig = (new SchemaConfig()) + ->setQuery($queryType) ->setMutation($mutationType) ->setSubscription($subscriptionType) ->setTypes($this->typeMap) ->setDirectives($directives) - ->setAssumeValid( - isset($this->options) - && isset($this->options['assumeValid']) - && $this->options['assumeValid'] - ); + ->setAssumeValid($this->options['assumeValid'] ?? false); return new Schema($schemaConfig); } @@ -204,6 +195,16 @@ private function getNamedType(string $typeName): NamedType return $this->typeMap[$typeName]; } + /** + * @param array $type + */ + public static function invalidOrIncompleteIntrospectionResult(array $type): InvariantViolation + { + return 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) . '.' + ); + } + /** * @param array $typeRef */ @@ -254,34 +255,39 @@ public function getInterfaceType(array $typeRef): InterfaceType /** * @param array $type + * + * @return Type&NamedType */ private function buildType(array $type): NamedType { - if (array_key_exists('name', $type) && array_key_exists('kind', $type)) { - switch ($type['kind']) { - case TypeKind::SCALAR: - return $this->buildScalarDef($type); + if (! array_key_exists('kind', $type)) { + throw self::invalidOrIncompleteIntrospectionResult($type); + } - case TypeKind::OBJECT: - return $this->buildObjectDef($type); + switch ($type['kind']) { + case TypeKind::SCALAR: + return $this->buildScalarDef($type); - case TypeKind::INTERFACE: - return $this->buildInterfaceDef($type); + case TypeKind::OBJECT: + return $this->buildObjectDef($type); - case TypeKind::UNION: - return $this->buildUnionDef($type); + case TypeKind::INTERFACE: + return $this->buildInterfaceDef($type); - case TypeKind::ENUM: - return $this->buildEnumDef($type); + case TypeKind::UNION: + return $this->buildUnionDef($type); - case TypeKind::INPUT_OBJECT: - return $this->buildInputObjectDef($type); - } - } + case TypeKind::ENUM: + return $this->buildEnumDef($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) . '.' - ); + case TypeKind::INPUT_OBJECT: + return $this->buildInputObjectDef($type); + + default: + throw new InvariantViolation( + 'Invalid or incomplete introspection result. Received type with unknown kind: ' . json_encode($type) . '.' + ); + } } /** diff --git a/src/Utils/TypeInfo.php b/src/Utils/TypeInfo.php index 7a4222ad5..bbec1f30f 100644 --- a/src/Utils/TypeInfo.php +++ b/src/Utils/TypeInfo.php @@ -43,7 +43,6 @@ use function array_pop; use function count; use function is_array; -use function sprintf; class TypeInfo { @@ -131,7 +130,7 @@ public static function extractTypes(Type $type, array $typeMap = []): array if (isset($typeMap[$type->name])) { Utils::invariant( $typeMap[$type->name] === $type, - sprintf('Schema must contain unique named types but contains multiple types named "%s" ', $type) . + 'Schema must contain unique named types but contains multiple types named "' . $type . '" ' . '(see https://webonyx.github.io/graphql-php/type-definitions/#type-registry).' ); diff --git a/tests/Executor/ExecutorLazySchemaTest.php b/tests/Executor/ExecutorLazySchemaTest.php index 2c8d0f7f8..7254acffb 100644 --- a/tests/Executor/ExecutorLazySchemaTest.php +++ b/tests/Executor/ExecutorLazySchemaTest.php @@ -363,9 +363,7 @@ public function testDeepQuery(): void { $schema = new Schema([ 'query' => $this->loadType('Query'), - 'typeLoader' => function (string $name): Type { - return $this->loadType($name, true); - }, + 'typeLoader' => fn (string $name): Type => $this->loadType($name, true), ]); $query = '{ object { object { object { string } } } }'; @@ -377,17 +375,26 @@ public function testDeepQuery(): void $rootValue ); - self::assertEquals(['data' => $rootValue], $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE)); - self::assertEquals([ - 'Query' => true, - 'SomeObject' => true, - 'OtherObject' => true, - ], $this->loadedTypes); - self::assertEquals([ - 'Query.fields', - 'SomeObject', - 'SomeObject.fields', - ], $this->calls); + self::assertEquals( + ['data' => $rootValue], + $result->toArray(DebugFlag::INCLUDE_DEBUG_MESSAGE) + ); + self::assertEquals( + [ + 'Query' => true, + 'SomeObject' => true, + 'OtherObject' => true, + ], + $this->loadedTypes + ); + self::assertEquals( + [ + 'Query.fields', + 'SomeObject', + 'SomeObject.fields', + ], + $this->calls + ); } public function testResolveUnion(): void diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index 6fd478a86..cf0b23189 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -171,7 +171,7 @@ public function testIncludesStandardTypesOnlyIfTheyAreUsed(): void } '); - self::assertNull($clientSchema->getType('Int')); + self::assertArrayNotHasKey('Int', $clientSchema->getTypeMap()); } /** @@ -720,6 +720,50 @@ public function testThrowsWhenMissingKind(): void BuildClientSchema::build($introspection); } + public function testThrowsOnUnknownKind(): void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $queryTypeIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; + } + + $queryTypeIntrospection = &$type; + } + + self::assertArrayHasKey('kind', $queryTypeIntrospection); + + $queryTypeIntrospection['kind'] = 3; + + $this->expectExceptionMessageMatches( + '/Invalid or incomplete introspection result. Received type with unknown kind: {"kind":3,"name":"Query",.*}\./' + ); + BuildClientSchema::build($introspection); + } + + public function testThrowsWhenMissingName(): void + { + $introspection = Introspection::fromSchema(self::dummySchema()); + $queryTypeIntrospection = null; + foreach ($introspection['__schema']['types'] as &$type) { + if ($type['name'] !== 'Query') { + continue; + } + + $queryTypeIntrospection = &$type; + } + + self::assertArrayHasKey('name', $queryTypeIntrospection); + + unset($queryTypeIntrospection['name']); + + $this->expectExceptionMessageMatches( + '/Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: {"kind":"OBJECT",.*}\./' + ); + BuildClientSchema::build($introspection); + } + /** * it('throws when missing interfaces', () => { */ From 1889acaf611c3b61676193b07739f1c59aeacadc Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Fri, 15 Oct 2021 11:39:08 +0200 Subject: [PATCH 3/6] Remove var_dump --- src/Type/Introspection.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 5d277a470..27be05c6f 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -214,9 +214,6 @@ public static function fromSchema(Schema $schema, array $options = []): ?array $schema, self::getIntrospectionQuery($optionsWithDefaults) ); - foreach ($result->errors as $error) { - var_dump($error->getMessage()); - } return $result->data; } From 2aa1b4656b3b7bfe9f8bde5fb3d07e0ec275adc2 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Fri, 15 Oct 2021 11:39:56 +0200 Subject: [PATCH 4/6] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b40041cff..2b4dcb226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ You can find and compare releases at the [GitHub release page](https://github.co - Move class `BlockString` from namespace `GraphQL\Utils` to `GraphQL\Language` - Return string-keyed arrays from `GraphQL::getStandardDirectives()`, `GraphQL::getStandardTypes()` and `GraphQL::getStandardValidationRules()` - Move complexity related code from `FieldDefinition` to `QueryComplexity` +- Exclude unused standard types from the schema ### Added From a182e911fb0d6ccf6299a0d694f93da0a76d5034 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Fri, 15 Oct 2021 11:40:24 +0200 Subject: [PATCH 5/6] fix codestyle --- src/Type/Introspection.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php index 27be05c6f..93a5c2d50 100644 --- a/src/Type/Introspection.php +++ b/src/Type/Introspection.php @@ -33,7 +33,6 @@ use function array_merge; use function array_values; use function method_exists; -use function var_dump; class Introspection { From cb236ae772ad54919bd34a894d41bffefbd79f4e Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 18 Oct 2021 18:54:01 +0200 Subject: [PATCH 6/6] Document difference with graphql-js --- tests/Utils/BuildClientSchemaTest.php | 3 +++ tests/Utils/BuildSchemaTest.php | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/Utils/BuildClientSchemaTest.php b/tests/Utils/BuildClientSchemaTest.php index cf0b23189..5c21c6d76 100644 --- a/tests/Utils/BuildClientSchemaTest.php +++ b/tests/Utils/BuildClientSchemaTest.php @@ -172,6 +172,9 @@ public function testIncludesStandardTypesOnlyIfTheyAreUsed(): void '); self::assertArrayNotHasKey('Int', $clientSchema->getTypeMap()); + + self::markTestIncomplete('TODO we differ from graphql-js due to lazy loading, see https://github.com/webonyx/graphql-php/issues/964#issuecomment-945969162'); + self::assertNull($clientSchema->getType('Int')); } /** diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php index a26c29b1e..f351137d7 100644 --- a/tests/Utils/BuildSchemaTest.php +++ b/tests/Utils/BuildSchemaTest.php @@ -92,6 +92,25 @@ public function testSimpleType(): void self::assertEquals($output, $body); } + /** + * @see it('include standard type only if it is used') + */ + public function testIncludeStandardTypeOnlyIfItIsUsed(): void + { + $schema = BuildSchema::build('type Query'); + + // String and Boolean are always included through introspection types + $typeMap = $schema->getTypeMap(); + self::assertArrayNotHasKey('Int', $typeMap); + self::assertArrayNotHasKey('Float', $typeMap); + self::assertArrayNotHasKey('ID', $typeMap); + + self::markTestIncomplete('TODO we differ from graphql-js due to lazy loading, see https://github.com/webonyx/graphql-php/issues/964#issuecomment-945969162'); + self::assertNull($schema->getType('Int')); + self::assertNull($schema->getType('Float')); + self::assertNull($schema->getType('ID')); + } + private function cycleOutput($body, $options = []) { $ast = Parser::parse($body);