diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1dc47e6a..902f6105 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,7 +35,6 @@ jobs: matrix: php: ${{ fromJson(needs.supported-versions-matrix.outputs.version) }} laravel: [^6.0, ^8.0, ^9.0, ^10.0] - lazy_types: ['false', 'true'] exclude: - php: 7.4 laravel: ^9.0 @@ -49,7 +48,7 @@ jobs: laravel: ^10.0 - php: 7.4 laravel: ^10.0 - name: P=${{ matrix.php }} L=${{ matrix.laravel }} Lazy types=${{ matrix.lazy_types }} + name: P=${{ matrix.php }} L=${{ matrix.laravel }} runs-on: ubuntu-latest env: COMPOSER_NO_INTERACTION: 1 @@ -83,9 +82,5 @@ jobs: - run: composer update --prefer-dist --no-progress - - name: Enable lazy types conditionally - run: echo "TESTS_ENABLE_LAZYLOAD_TYPES=1" >> $GITHUB_ENV - if: matrix.lazy_types == 'true' - - name: Run tests run: composer tests diff --git a/CHANGELOG.md b/CHANGELOG.md index a88e5dc1..f2b2b8c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,51 @@ CHANGELOG [Next release](https://github.com/rebing/graphql-laravel/compare/8.6.0...master) -------------- +## Breaking changes +### Added +- Upgrade to graphql-php 15 [\#953 / mfn](https://github.com/rebing/graphql-laravel/pull/953)\ + This includes possible breaking changes also outside of this package, see also https://github.com/webonyx/graphql-php/releases/tag/v15.0.0 \ + Known breaking changes: + - non-standard error related data keys are not included directly in + `errors.*.` any more, but have been moved to + `errors.*.extensions.`.\ + Also new keys may appear here from upstream. + - The `errors.*.extensions.category` has been removed upstream, but we try to + keep it alive with the interface + `\Rebing\GraphQL\Error\ProvidesErrorCategory` as it can be a useful + discriminator on the client side in certain cases. But only the cases from + _this_ library are preserved, e.g. categories like `request`, `graphql` or + `internal` are gone. + - The `\Rebing\GraphQL\Support\OperationParams` has added required types due to + its base class changes: + - Old: `public function getOriginalInput($key)`\ + new: `public function getOriginalInput(string $key)` + - Old: `public function isReadOnly()`\ + new: `public function isReadOnly(): bool` + + Some BC may happen also if you extended code originating in graphql-php, + some examples: + - if you implement custom types, you now have to use property types for e.g. + `$name` or `$description` + - If you used any `\GraphQL\Validator\DocumentValidator` in your code + directly, you now need use FQCN to reference them and not the shortened + string names. + - `->getWrappedType(true)` was replaced with `->getInnermostType()` + - the class `\GraphQL\Type\Definition\FieldArgument` has been renamed to + `\GraphQL\Type\Definition\Argument` + +### Removed +- Remove support for eager loading (=non-lazy loading) of types\ + Lazy loading has been introduced in 2.0.0 (2019-08) and has been made the + default since 8.0.0 (2021-11).\ + The practical impact is that types are always going to be resolved using a + type loader and therefore cannot use aliases anymore. Types and their type + name have to match. + +## Changed +- The type resolver is now able to resolve the top level types 'Query', + 'Mutation' and 'Subscription' + 2023-02-18, 8.6.0 ----------------- ### Added diff --git a/README.md b/README.md index 74dbc8ef..3e8883cd 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,6 @@ The default GraphiQL view makes use of the global `csrf_token()` helper function - [Upgrading from v1 to v2](#upgrading-from-v1-to-v2) - [Migrating from Folklore](#migrating-from-folklore) - [Performance considerations](#performance-considerations) - - [Lazy loading of types](#lazy-loading-of-types) - - [Example of aliasing **not** supported by lazy loading](#example-of-aliasing-not-supported-by-lazy-loading) - [Wrap Types](#wrap-types) - [GraphQL testing clients](#graphql-testing-clients) @@ -278,12 +276,6 @@ them, in addition to the global middleware. For example: 'default' => [ 'query' => [ ExampleQuery::class, - // It's possible to specify a name/alias with the key - // but this is discouraged as it prevents things - // like improving performance with e.g. `lazyload_types=true` - // It's recommended to specify just the class here and - // rely on the `'name'` attribute in the query / type. - 'someQuery' => AnotherExampleQuery::class, ], 'mutation' => [ ExampleMutation::class, @@ -447,18 +439,6 @@ Alternatively you can: GraphQL::addType(\App\GraphQL\Types\UserType::class); ``` -As with queries/mutations, you can use an alias name (though again this prevents -it from taking advantage of lazy type loading): -```php -'schemas' => [ - 'default' => [ - // ... - - 'types' => [ - 'Useralias' => App\GraphQL\Types\UserType::class, - ], -``` - Then you need to define a query that returns this type (or a list). You can also specify arguments that you can use in the resolve method. ```php namespace App\GraphQL\Queries; @@ -2696,9 +2676,6 @@ To prevent such scenarios, you can add the `UnusedVariablesMiddleware` to your - `batching`\ - 'enable'\ Whether to support GraphQL batching or not -- `lazyload_types`\ - The types will be loaded on demand. Enabled by default as it improves - performance. Cannot be used with type aliasing. - `error_formatter`\ This callable will be passed the Error object for each errors GraphQL catch. The method should return an array representing the error. @@ -2824,24 +2801,6 @@ The following is not a bullet-proof list but should serve as a guide. It's not a ## Performance considerations -### Lazy loading of types - -Lazy loading of types is a way of improving the start up performance. - -If you are declaring types using aliases, this is not supported and you need to -set `lazyload_types` set to `false`. - -#### Example of aliasing **not** supported by lazy loading - -I.e. you cannot have a query class `ExampleQuery` with the `$name` property -`example` but register it with a different one; this will **not** work: - -```php -'query' => [ - 'aliasedExample' => ExampleQuery::class, -], -``` - ### Wrap Types You can wrap types to add more information to the queries and mutations. Similar as the pagination is working you can do the same with your extra data that you want to inject ([see test examples](https://github.com/rebing/graphql-laravel/tree/master/tests/Unit/WithTypeTests)). For instance, in your query: diff --git a/composer.json b/composer.json index c692d0f9..1cacb32b 100644 --- a/composer.json +++ b/composer.json @@ -38,9 +38,9 @@ "ext-json": "*", "illuminate/contracts": "^6.0|^8.0|^9.0|^10.0", "illuminate/support": "^6.0|^8.0|^9.0|^10.0", - "laragraph/utils": "^1", + "laragraph/utils": "^1.5", "thecodingmachine/safe": "^1.1|^2.4", - "webonyx/graphql-php": "^14.6.4" + "webonyx/graphql-php": "^15" }, "require-dev": { "ext-pdo_sqlite": "*", @@ -69,11 +69,7 @@ "phpstan-baseline": "phpstan analyse --memory-limit=512M --generate-baseline", "lint": "php-cs-fixer fix --diff --dry-run", "fix-style": "php-cs-fixer fix", - "tests": "phpunit --colors=always --verbose", - "tests-all": [ - "TESTS_ENABLE_LAZYLOAD_TYPES=0 phpunit --colors=always --verbose", - "TESTS_ENABLE_LAZYLOAD_TYPES=1 phpunit --colors=always --verbose" - ] + "tests": "phpunit --colors=always --verbose" }, "extra": { "branch-alias": { diff --git a/config/config.php b/config/config.php index 551e9775..03ddeddc 100644 --- a/config/config.php +++ b/config/config.php @@ -111,11 +111,6 @@ // \Rebing\GraphQL\Support\UploadType::class, ], - // The types will be loaded on demand. Default is to load all types on each request - // Can increase performance on schemes with many types - // Presupposes the config type key to match the type class name property - 'lazyload_types' => true, - // This callable will be passed the Error object for each errors GraphQL catch. // The method should return an array representing the error. // Typically: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e40a9787..752b4483 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10,6 +10,21 @@ parameters: count: 1 path: src/GraphQL.php + - + message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\ObjectType constructor expects array\\{name\\?\\: string\\|null, description\\?\\: string\\|null, resolveField\\?\\: \\(callable\\(mixed, array\\, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: mixed\\)\\|null, fields\\: \\(callable\\(\\)\\: iterable\\)\\|iterable, interfaces\\?\\: \\(callable\\(\\)\\: iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>\\)\\|iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>, isTypeOf\\?\\: \\(callable\\(mixed, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: bool\\|GraphQL\\\\Deferred\\|null\\)\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|null, extensionASTNodes\\?\\: array\\\\|null\\}, non\\-empty\\-array\\\\|\\(ArrayAccess&Rebing\\\\GraphQL\\\\Support\\\\Field\\)\\>\\|string\\> given\\.$#" + count: 1 + path: src/GraphQL.php + + - + message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Schema constructor expects array\\{query\\?\\: GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null, mutation\\?\\: GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null, subscription\\?\\: GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|null, types\\?\\: \\(callable\\(\\)\\: iterable\\\\)\\|iterable\\\\|null, directives\\?\\: array\\\\|null, typeLoader\\?\\: \\(callable\\(string\\)\\: \\(GraphQL\\\\Type\\\\Definition\\\\NamedType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|null\\)\\|null, assumeValid\\?\\: bool\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\SchemaDefinitionNode\\|null, \\.\\.\\.\\}\\|GraphQL\\\\Type\\\\SchemaConfig, array\\{query\\: GraphQL\\\\Type\\\\Definition\\\\Type, mutation\\: GraphQL\\\\Type\\\\Definition\\\\Type\\|null, subscription\\: GraphQL\\\\Type\\\\Definition\\\\Type\\|null, directives\\: array\\, types\\: Closure\\(\\)\\: list\\, typeLoader\\: Closure\\(mixed\\)\\: GraphQL\\\\Type\\\\Definition\\\\Type\\|null\\} given\\.$#" + count: 1 + path: src/GraphQL.php + + - + message: "#^Property GraphQL\\\\Type\\\\Definition\\\\ObjectType\\:\\:\\$config \\(array\\{name\\?\\: string\\|null, description\\?\\: string\\|null, resolveField\\?\\: \\(callable\\(mixed, array\\, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: mixed\\)\\|null, fields\\: \\(callable\\(\\)\\: iterable\\)\\|iterable, interfaces\\?\\: \\(callable\\(\\)\\: iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>\\)\\|iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>, isTypeOf\\?\\: \\(callable\\(mixed, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: bool\\|GraphQL\\\\Deferred\\|null\\)\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|null, extensionASTNodes\\?\\: array\\\\|null\\}\\) does not accept non\\-empty\\-array\\, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: mixed\\)\\|\\(callable\\(mixed, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: bool\\|GraphQL\\\\Deferred\\|null\\)\\|GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|iterable\\|string\\|null\\>\\.$#" + count: 1 + path: src/GraphQL.php + - message: "#^Property Rebing\\\\GraphQL\\\\GraphQL\\:\\:\\$types \\(array\\\\) does not accept array\\\\.$#" count: 1 @@ -75,6 +90,11 @@ parameters: count: 1 path: src/Support/AliasArguments/ArrayKeyChange.php + - + message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\EnumType constructor expects array\\{name\\?\\: string\\|null, description\\?\\: string\\|null, values\\: \\(callable\\(\\)\\: iterable\\\\)\\|iterable\\, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\EnumTypeDefinitionNode\\|null, extensionASTNodes\\?\\: array\\\\|null\\}, array\\ given\\.$#" + count: 1 + path: src/Support/EnumType.php + - message: "#^Parameter \\#2 \\$schema of method Rebing\\\\GraphQL\\\\Support\\\\ExecutionMiddleware\\\\AbstractExecutionMiddleware\\:\\:handle\\(\\) expects GraphQL\\\\Type\\\\Schema, Closure given\\.$#" count: 1 @@ -150,6 +170,16 @@ parameters: count: 1 path: src/Support/Field.php + - + message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\InputObjectType constructor expects array\\{name\\?\\: string\\|null, description\\?\\: string\\|null, fields\\: \\(callable\\(\\)\\: iterable\\\\)\\|iterable\\, parseValue\\?\\: callable\\(array\\\\)\\: mixed, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\InputObjectTypeDefinitionNode\\|null, extensionASTNodes\\?\\: array\\\\|null\\}, array\\ given\\.$#" + count: 1 + path: src/Support/InputType.php + + - + message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\InterfaceType constructor expects array\\{name\\?\\: string\\|null, description\\?\\: string\\|null, fields\\: \\(callable\\(\\)\\: iterable\\)\\|iterable, interfaces\\?\\: \\(callable\\(\\)\\: iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>\\)\\|iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>, resolveType\\?\\: \\(callable\\(mixed, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: \\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|string\\|null\\)\\|GraphQL\\\\Deferred\\|GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|string\\|null\\)\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\InterfaceTypeDefinitionNode\\|null, extensionASTNodes\\?\\: array\\\\|null\\}, array\\ given\\.$#" + count: 1 + path: src/Support/InterfaceType.php + - message: "#^Method Rebing\\\\GraphQL\\\\Support\\\\Middleware\\:\\:handle\\(\\) has no return type specified\\.$#" count: 1 @@ -195,6 +225,11 @@ parameters: count: 1 path: src/Support/Privacy.php + - + message: "#^Access to an undefined property GraphQL\\\\Type\\\\Definition\\\\Type\\:\\:\\$config\\.$#" + count: 1 + path: src/Support/SelectFields.php + - message: "#^Method Rebing\\\\GraphQL\\\\Support\\\\SelectFields\\:\\:__construct\\(\\) has parameter \\$queryArgs with no value type specified in iterable type array\\.$#" count: 1 @@ -285,6 +320,21 @@ parameters: count: 1 path: src/Support/SelectFields.php + - + message: "#^Offset 'always' on array\\{name\\: string, type\\: \\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\OutputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\OutputType&GraphQL\\\\Type\\\\Definition\\\\Type\\), resolve\\?\\: \\(callable\\(mixed, array\\, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: mixed\\)\\|null, args\\?\\: iterable\\\\|null, description\\?\\: string\\|null, deprecationReason\\?\\: string\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\|null, complexity\\?\\: \\(callable\\(int, array\\\\)\\: int\\)\\|null\\} in isset\\(\\) does not exist\\.$#" + count: 1 + path: src/Support/SelectFields.php + + - + message: "#^Offset 'privacy' on array\\{name\\: string, type\\: \\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\OutputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\OutputType&GraphQL\\\\Type\\\\Definition\\\\Type\\), resolve\\?\\: \\(callable\\(mixed, array\\, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: mixed\\)\\|null, args\\?\\: iterable\\\\|null, description\\?\\: string\\|null, deprecationReason\\?\\: string\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\|null, complexity\\?\\: \\(callable\\(int, array\\\\)\\: int\\)\\|null\\} in isset\\(\\) does not exist\\.$#" + count: 1 + path: src/Support/SelectFields.php + + - + message: "#^Offset 'selectable' on array\\{name\\: string, type\\: \\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\OutputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\OutputType&GraphQL\\\\Type\\\\Definition\\\\Type\\), resolve\\?\\: \\(callable\\(mixed, array\\, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: mixed\\)\\|null, args\\?\\: iterable\\\\|null, description\\?\\: string\\|null, deprecationReason\\?\\: string\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\FieldDefinitionNode\\|null, complexity\\?\\: \\(callable\\(int, array\\\\)\\: int\\)\\|null\\} in isset\\(\\) does not exist\\.$#" + count: 1 + path: src/Support/SelectFields.php + - message: "#^Parameter \\#1 \\$callback of function call_user_func expects callable\\(\\)\\: mixed, array\\{mixed, mixed\\} given\\.$#" count: 1 @@ -300,6 +350,11 @@ parameters: count: 1 path: src/Support/SelectFields.php + - + message: "#^Result of && is always false\\.$#" + count: 1 + path: src/Support/SelectFields.php + - message: "#^Anonymous function never returns null so it can be removed from the return type\\.$#" count: 2 @@ -321,9 +376,14 @@ parameters: path: src/Support/Type.php - - message: "#^Parameter \\#2 \\$nodes of class GraphQL\\\\Error\\\\Error constructor expects GraphQL\\\\Language\\\\AST\\\\Node\\|\\(iterable\\&Traversable\\)\\|null, array\\ given\\.$#" + message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\ObjectType constructor expects array\\{name\\?\\: string\\|null, description\\?\\: string\\|null, resolveField\\?\\: \\(callable\\(mixed, array\\, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: mixed\\)\\|null, fields\\: \\(callable\\(\\)\\: iterable\\)\\|iterable, interfaces\\?\\: \\(callable\\(\\)\\: iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>\\)\\|iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>, isTypeOf\\?\\: \\(callable\\(mixed, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: bool\\|GraphQL\\\\Deferred\\|null\\)\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|null, extensionASTNodes\\?\\: array\\\\|null\\}, array\\ given\\.$#" count: 1 - path: src/Support/UploadType.php + path: src/Support/Type.php + + - + message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\UnionType constructor expects array\\{name\\?\\: string\\|null, description\\?\\: string\\|null, types\\: \\(callable\\(\\)\\: iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\ObjectType\\)\\|GraphQL\\\\Type\\\\Definition\\\\ObjectType\\>\\)\\|iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\ObjectType\\)\\|GraphQL\\\\Type\\\\Definition\\\\ObjectType\\>, resolveType\\?\\: \\(callable\\(mixed, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: \\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|string\\|null\\)\\|GraphQL\\\\Deferred\\|GraphQL\\\\Type\\\\Definition\\\\ObjectType\\|string\\|null\\)\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\UnionTypeDefinitionNode\\|null, extensionASTNodes\\?\\: array\\\\|null\\}, array\\ given\\.$#" + count: 1 + path: src/Support/UnionType.php - message: "#^Unsafe usage of new static\\(\\)\\.$#" @@ -705,6 +765,11 @@ parameters: count: 1 path: tests/Database/SelectFields/ValidateFieldTests/ValidateFieldsQuery.php + - + message: "#^Parameter \\#1 \\$config of method GraphQL\\\\Type\\\\Definition\\\\Directive\\:\\:__construct\\(\\) expects array\\{name\\: string, description\\?\\: string\\|null, args\\?\\: iterable\\\\|null, locations\\: array\\, isRepeatable\\?\\: bool\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\DirectiveDefinitionNode\\|null\\}, array\\{name\\: 'exampleDirective', description\\: 'This is an example…', locations\\: array\\{'QUERY'\\}, args\\: array\\{first\\: GraphQL\\\\Type\\\\Definition\\\\Argument\\}\\} given\\.$#" + count: 1 + path: tests/Support/Directives/ExampleDirective.php + - message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Objects\\\\CustomExamplesQuery\\:\\:resolve\\(\\) has no return type specified\\.$#" count: 1 @@ -890,21 +955,6 @@ parameters: count: 1 path: tests/Support/Objects/ExamplesAuthorizeQuery.php - - - message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Objects\\\\ExamplesConfigAliasQuery\\:\\:resolve\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/Support/Objects/ExamplesConfigAliasQuery.php - - - - message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Objects\\\\ExamplesConfigAliasQuery\\:\\:resolve\\(\\) has parameter \\$args with no type specified\\.$#" - count: 1 - path: tests/Support/Objects/ExamplesConfigAliasQuery.php - - - - message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Objects\\\\ExamplesConfigAliasQuery\\:\\:resolve\\(\\) has parameter \\$root with no type specified\\.$#" - count: 1 - path: tests/Support/Objects/ExamplesConfigAliasQuery.php - - message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Objects\\\\ExamplesFilteredQuery\\:\\:resolve\\(\\) has parameter \\$args with no type specified\\.$#" count: 1 @@ -1336,15 +1386,25 @@ parameters: path: tests/Unit/EngineErrorInResolverTests/QueryWithEngineErrorInCodeQuery.php - - message: "#^Offset 'index' might not exist on array\\|null\\.$#" + message: "#^Parameter \\#2 \\$visitor of static method GraphQL\\\\Language\\\\Visitor\\:\\:visit\\(\\) expects array\\\\|\\(callable\\(GraphQL\\\\Language\\\\AST\\\\Node\\)\\: GraphQL\\\\Language\\\\VisitorOperation\\|void\\|false\\|null\\)\\>, array\\{VariableDefinition\\: Closure\\(mixed, mixed, mixed, mixed, mixed\\)\\: mixed\\} given\\.$#" count: 1 - path: tests/Unit/ExecutionMiddlewareTest/ChangeVariableMiddleware.php + path: tests/Unit/ExecutionMiddlewareTest/ChangeQueryArgTypeMiddleware.php + + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertIsArray\\(\\) with non\\-empty\\-array will always evaluate to true\\.$#" + count: 1 + path: tests/Unit/GraphQLTest.php - message: "#^Parameter \\#1 \\$abstract of function app expects string\\|null, object\\|string given\\.$#" count: 3 path: tests/Unit/GraphQLTest.php + - + message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\ObjectType constructor expects array\\{name\\?\\: string\\|null, description\\?\\: string\\|null, resolveField\\?\\: \\(callable\\(mixed, array\\, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: mixed\\)\\|null, fields\\: \\(callable\\(\\)\\: iterable\\)\\|iterable, interfaces\\?\\: \\(callable\\(\\)\\: iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>\\)\\|iterable\\<\\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\)\\|GraphQL\\\\Type\\\\Definition\\\\InterfaceType\\>, isTypeOf\\?\\: \\(callable\\(mixed, mixed, GraphQL\\\\Type\\\\Definition\\\\ResolveInfo\\)\\: bool\\|GraphQL\\\\Deferred\\|null\\)\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\ObjectTypeDefinitionNode\\|null, extensionASTNodes\\?\\: array\\\\|null\\}, array\\{name\\: 'ObjectType'\\} given\\.$#" + count: 1 + path: tests/Unit/GraphQLTest.php + - message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Unit\\\\InstantiableTypesTest\\\\FormattableDate\\:\\:__construct\\(\\) has parameter \\$settings with no value type specified in iterable type array\\.$#" count: 1 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0d899562..e3d78834 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -24,8 +24,6 @@ parameters: - '/Trying to invoke Closure\|null but it might not be a callable/' - '/Property Rebing\\GraphQL\\Support\\Field\:\:\$name \(string\) does not accept int\|string/' - '/Parameter #1 \$name of method Rebing\\GraphQL\\Support\\Type\:\:getFieldResolver\(\) expects string, int\|string given/' - # tests/Unit/EndpointTest.php - - '/Parameter #1 \$wrappedType of static method GraphQL\\Type\\Definition\\Type::nonNull\(\) expects \(callable\(\): mixed\)\|GraphQL\\Type\\Definition\\NullableType, GraphQL\\Type\\Definition\\Type given/' # Mass ignore the raw array property access used in many tests for now # See also https://github.com/nunomaduro/larastan/issues/611 - @@ -36,5 +34,7 @@ parameters: message: '/Cannot call method make\(\) on Illuminate\\Foundation\\Application\|null\./' - path: tests/* - message: "/Offset 'config' might not exist on Illuminate.*Application\\|null./" + message: "/Offset 'config' might not exist/" + - path: tests/* + message: '/Parameter #1 \$type of static method GraphQL\\Type\\Definition\\Type::nonNull\(\) expects .*, GraphQL\\Type\\Definition\\Type given./' reportUnmatchedIgnoredErrors: true diff --git a/src/Error/AuthorizationError.php b/src/Error/AuthorizationError.php index d97ffb3f..896ca20f 100644 --- a/src/Error/AuthorizationError.php +++ b/src/Error/AuthorizationError.php @@ -5,7 +5,7 @@ use GraphQL\Error\Error; -class AuthorizationError extends Error +class AuthorizationError extends Error implements ProvidesErrorCategory { public function isClientSafe(): bool { diff --git a/src/Error/AutomaticPersistedQueriesError.php b/src/Error/AutomaticPersistedQueriesError.php index 54831a6d..2841b732 100644 --- a/src/Error/AutomaticPersistedQueriesError.php +++ b/src/Error/AutomaticPersistedQueriesError.php @@ -5,7 +5,7 @@ use GraphQL\Error\Error; -class AutomaticPersistedQueriesError extends Error +class AutomaticPersistedQueriesError extends Error implements ProvidesErrorCategory { public const CODE_PERSISTED_QUERY_NOT_SUPPORTED = 'PERSISTED_QUERY_NOT_SUPPORTED'; public const CODE_PERSISTED_QUERY_NOT_FOUND = 'PERSISTED_QUERY_NOT_FOUND'; diff --git a/src/Error/ProvidesErrorCategory.php b/src/Error/ProvidesErrorCategory.php new file mode 100644 index 00000000..da51c629 --- /dev/null +++ b/src/Error/ProvidesErrorCategory.php @@ -0,0 +1,9 @@ +types[$name])) { - $error = "Type $name not found."; - - if ($this->config->get('graphql.lazyload_types', true)) { - $error .= "\nCheck that the config array key for the type matches the name attribute in the type's class.\nIt is required when 'lazyload_types' is enabled"; - } + $error = "Type $name not found. Check that the config array key for the type matches the name attribute in the type's class."; throw new TypeNotFound($error); } @@ -401,11 +398,25 @@ public function buildSchemaFromConfig(array $schemaConfig): Schema return $types; }, - 'typeLoader' => $this->config->get('graphql.lazyload_types', true) - ? function ($name) { - return $this->type($name); + 'typeLoader' => function ($name) use ( + $query, + $mutation, + $subscription + ) { + switch ($name) { + case 'Query': + return $query; + + case 'Mutation': + return $mutation; + + case 'Subscription': + return $subscription; + + default: + return $this->type($name); } - : null, + }, ]); } @@ -520,6 +531,12 @@ public static function formatError(Error $e): array if ($previous instanceof ValidationError) { $error['extensions']['validation'] = $previous->getValidatorMessages()->getMessages(); } + + if ($previous instanceof ProvidesErrorCategory) { + $error['extensions']['category'] = $previous->getCategory(); + } + } elseif ($e instanceof ProvidesErrorCategory) { + $error['extensions']['category'] = $e->getCategory(); } return $error; diff --git a/src/GraphQLServiceProvider.php b/src/GraphQLServiceProvider.php index 34f54d1a..08cce9c4 100644 --- a/src/GraphQLServiceProvider.php +++ b/src/GraphQLServiceProvider.php @@ -77,7 +77,7 @@ protected function applySecurityRules(Repository $config): void if (null !== $maxQueryComplexity) { /** @var QueryComplexity $queryComplexity */ - $queryComplexity = DocumentValidator::getRule('QueryComplexity'); + $queryComplexity = DocumentValidator::getRule(QueryComplexity::class); $queryComplexity->setMaxQueryComplexity($maxQueryComplexity); } @@ -85,7 +85,7 @@ protected function applySecurityRules(Repository $config): void if (null !== $maxQueryDepth) { /** @var QueryDepth $queryDepth */ - $queryDepth = DocumentValidator::getRule('QueryDepth'); + $queryDepth = DocumentValidator::getRule(QueryDepth::class); $queryDepth->setMaxQueryDepth($maxQueryDepth); } @@ -93,7 +93,7 @@ protected function applySecurityRules(Repository $config): void if (true === $disableIntrospection) { /** @var DisableIntrospection $disableIntrospection */ - $disableIntrospection = DocumentValidator::getRule('DisableIntrospection'); + $disableIntrospection = DocumentValidator::getRule(DisableIntrospection::class); $disableIntrospection->setEnabled(DisableIntrospection::ENABLED); } } diff --git a/src/Support/AliasArguments/AliasArguments.php b/src/Support/AliasArguments/AliasArguments.php index e42e5d90..a38b71c3 100644 --- a/src/Support/AliasArguments/AliasArguments.php +++ b/src/Support/AliasArguments/AliasArguments.php @@ -119,7 +119,7 @@ private function isWrappedInList(Type $type): bool private function getWrappedType(Type $type): Type { if ($type instanceof WrappingType) { - $type = $type->getWrappedType(true); + $type = $type->getInnermostType(); } return $type; diff --git a/src/Support/OperationParams.php b/src/Support/OperationParams.php index 282e676b..1c8686c6 100644 --- a/src/Support/OperationParams.php +++ b/src/Support/OperationParams.php @@ -32,14 +32,17 @@ protected function init(BaseOperationParams $baseOperationParams): void $this->baseOperationParams = $baseOperationParams; } - public function getOriginalInput($key) + /** + * @return mixed|null + */ + public function getOriginalInput(string $key) { - return $this->baseOperationParams->getOriginalInput($key); + return $this->baseOperationParams->originalInput[$key] ?? null; } - public function isReadOnly() + public function isReadOnly(): bool { - return $this->baseOperationParams->isReadOnly(); + return $this->baseOperationParams->readOnly; } public function getParsedQuery(): DocumentNode diff --git a/src/Support/RulesInFields.php b/src/Support/RulesInFields.php index fae4ab49..5dd66c82 100644 --- a/src/Support/RulesInFields.php +++ b/src/Support/RulesInFields.php @@ -21,7 +21,7 @@ class RulesInFields public function __construct(Type $parentType, array $fieldsAndArgumentsSelection) { $this->parentType = $parentType instanceof WrappingType - ? $parentType->getWrappedType(true) + ? $parentType->getInnermostType() : $parentType; $this->fieldsAndArguments = $fieldsAndArgumentsSelection; } diff --git a/src/Support/SelectFields.php b/src/Support/SelectFields.php index 8c487028..303a21fc 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -41,7 +41,7 @@ class SelectFields public function __construct(GraphqlType $parentType, array $queryArgs, $ctx, array $fieldsAndArguments) { if ($parentType instanceof WrappingType) { - $parentType = $parentType->getWrappedType(true); + $parentType = $parentType->getInnermostType(); } $requestedFields = [ @@ -77,7 +77,7 @@ public static function getSelectableFieldsAndRelations( $with = []; if ($parentType instanceof WrappingType) { - $parentType = $parentType->getWrappedType(true); + $parentType = $parentType->getInnermostType(); } $parentTable = static::getTableNameFromParentType($parentType); $primaryKey = static::getPrimaryKeyFromParentType($parentType); @@ -164,7 +164,7 @@ protected static function handleFields( $parentTypeUnwrapped = $parentType; if ($parentTypeUnwrapped instanceof WrappingType) { - $parentTypeUnwrapped = $parentTypeUnwrapped->getWrappedType(true); + $parentTypeUnwrapped = $parentTypeUnwrapped->getInnermostType(); } // First check if the field is even accessible @@ -500,7 +500,7 @@ function (GraphqlType $type) use ($query) { } if ($newParentType instanceof WrappingType) { - $newParentType = $newParentType->getWrappedType(true); + $newParentType = $newParentType->getInnermostType(); } /** @var callable $callable */ diff --git a/src/Support/UploadType.php b/src/Support/UploadType.php index a6d614d2..5edba2a1 100644 --- a/src/Support/UploadType.php +++ b/src/Support/UploadType.php @@ -12,10 +12,8 @@ class UploadType extends ScalarType implements TypeConvertible { - /** @var string */ - public $name = 'Upload'; - /** @var string */ - public $description = + public string $name = 'Upload'; + public ?string $description = 'The `Upload` special type represents a file to be uploaded in the same HTTP request as specified by [graphql-multipart-request-spec](https://github.com/jaydenseric/graphql-multipart-request-spec).'; diff --git a/tests/Database/EmptyQueryTest.php b/tests/Database/EmptyQueryTest.php index 9e4427d5..d468d12c 100644 --- a/tests/Database/EmptyQueryTest.php +++ b/tests/Database/EmptyQueryTest.php @@ -37,7 +37,9 @@ public function testEmptyBatchedQuery(): void $results = array_map( static function (array $result): array { - unset($result['errors'][0]['trace']); + unset($result['errors'][0]['extensions']['file']); + unset($result['errors'][0]['extensions']['line']); + unset($result['errors'][0]['extensions']['trace']); return $result; }, @@ -50,7 +52,6 @@ static function (array $result): array { [ 'message' => 'GraphQL Request must include at least one of those two parameters: "query" or "queryId"', 'extensions' => [ - 'category' => 'request', ], ], ], @@ -60,7 +61,6 @@ static function (array $result): array { [ 'message' => 'GraphQL Request must include at least one of those two parameters: "query" or "queryId"', 'extensions' => [ - 'category' => 'request', ], ], ], @@ -70,7 +70,6 @@ static function (array $result): array { [ 'message' => 'GraphQL Request must include at least one of those two parameters: "query" or "queryId"', 'extensions' => [ - 'category' => 'request', ], ], ], @@ -80,7 +79,6 @@ static function (array $result): array { [ 'message' => 'GraphQL Request must include at least one of those two parameters: "query" or "queryId"', 'extensions' => [ - 'category' => 'request', ], ], ], @@ -90,7 +88,6 @@ static function (array $result): array { [ 'message' => 'Syntax Error: Unexpected ', 'extensions' => [ - 'category' => 'graphql', ], 'locations' => [ [ diff --git a/tests/Database/SelectFields/ValidateFieldTests/ValidateFieldTest.php b/tests/Database/SelectFields/ValidateFieldTests/ValidateFieldTest.php index e1c2e232..23906dfd 100644 --- a/tests/Database/SelectFields/ValidateFieldTests/ValidateFieldTest.php +++ b/tests/Database/SelectFields/ValidateFieldTests/ValidateFieldTest.php @@ -505,10 +505,9 @@ public function testPrivacyWrongType(): void $expectedResult = [ 'errors' => [ [ - 'debugMessage' => 'Unsupported use of \'privacy\' configuration on field \'title_privacy_wrong_type\'.', 'message' => 'Internal server error', 'extensions' => [ - 'category' => 'internal', + 'debugMessage' => 'Unsupported use of \'privacy\' configuration on field \'title_privacy_wrong_type\'.', ], 'locations' => [ [ diff --git a/tests/Database/SelectFieldsTest.php b/tests/Database/SelectFieldsTest.php index 499bbdf0..202cca70 100644 --- a/tests/Database/SelectFieldsTest.php +++ b/tests/Database/SelectFieldsTest.php @@ -164,15 +164,16 @@ public function testWithSelectFieldsNonInjectableTypehints(): void 'expectErrors' => true, ]); - unset($result['errors'][0]['trace']); + unset($result['errors'][0]['extensions']['file']); + unset($result['errors'][0]['extensions']['line']); + unset($result['errors'][0]['extensions']['trace']); $expectedResult = [ 'errors' => [ [ - 'debugMessage' => "'coolNumber' could not be injected", 'message' => 'Internal server error', 'extensions' => [ - 'category' => 'internal', + 'debugMessage' => "'coolNumber' could not be injected", ], 'locations' => [ [ diff --git a/tests/Support/Directives/ExampleDirective.php b/tests/Support/Directives/ExampleDirective.php index bdad2787..938a2ea6 100644 --- a/tests/Support/Directives/ExampleDirective.php +++ b/tests/Support/Directives/ExampleDirective.php @@ -4,8 +4,8 @@ namespace Rebing\GraphQL\Tests\Support\Directives; use GraphQL\Language\DirectiveLocation; +use GraphQL\Type\Definition\Argument; use GraphQL\Type\Definition\Directive; -use GraphQL\Type\Definition\FieldArgument; use GraphQL\Type\Definition\Type; class ExampleDirective extends Directive @@ -20,7 +20,7 @@ public function __construct() DirectiveLocation::QUERY, ], 'args' => [ - new FieldArgument([ + 'first' => new Argument([ 'name' => 'first', 'description' => 'Description of this argument', 'type' => Type::string(), diff --git a/tests/Support/Objects/ExampleType2.php b/tests/Support/Objects/ExampleType2.php deleted file mode 100644 index f42e7d55..00000000 --- a/tests/Support/Objects/ExampleType2.php +++ /dev/null @@ -1,26 +0,0 @@ - 'Example2', - 'description' => 'An example', - ]; - - public function fields(): array - { - return [ - 'test' => [ - 'type' => Type::string(), - 'description' => 'A test field', - ], - 'test_validation' => ExampleValidationField::class, - ]; - } -} diff --git a/tests/Support/Objects/ExamplesConfigAliasQuery.php b/tests/Support/Objects/ExamplesConfigAliasQuery.php deleted file mode 100644 index bd159e48..00000000 --- a/tests/Support/Objects/ExamplesConfigAliasQuery.php +++ /dev/null @@ -1,40 +0,0 @@ - 'examplesAlias', - ]; - - public function type(): Type - { - return Type::listOf(GraphQL::type('ExampleConfigAlias')); - } - - public function args(): array - { - return [ - 'index' => ['name' => 'index', 'type' => Type::int()], - ]; - } - - public function resolve($root, $args) - { - $data = include __DIR__ . '/data.php'; - - if (isset($args['index'])) { - return [ - $data[$args['index']], - ]; - } - - return $data; - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 92cabd07..65a470e0 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -18,13 +18,11 @@ use Rebing\GraphQL\Tests\Support\Objects\ExamplesAuthorizeMessageQuery; use Rebing\GraphQL\Tests\Support\Objects\ExamplesAuthorizeQuery; use Rebing\GraphQL\Tests\Support\Objects\ExampleSchema; -use Rebing\GraphQL\Tests\Support\Objects\ExamplesConfigAliasQuery; use Rebing\GraphQL\Tests\Support\Objects\ExamplesFilteredQuery; use Rebing\GraphQL\Tests\Support\Objects\ExamplesMiddlewareQuery; use Rebing\GraphQL\Tests\Support\Objects\ExamplesPaginationQuery; use Rebing\GraphQL\Tests\Support\Objects\ExamplesQuery; use Rebing\GraphQL\Tests\Support\Objects\ExampleType; -use Rebing\GraphQL\Tests\Support\Objects\ExampleType2; use Rebing\GraphQL\Tests\Support\Objects\UpdateExampleMutation; use Symfony\Component\Console\Tester\CommandTester; @@ -43,10 +41,6 @@ protected function setUp(): void protected function getEnvironmentSetUp($app): void { - if ('0' === env('TESTS_ENABLE_LAZYLOAD_TYPES')) { - $app['config']->set('graphql.lazyload_types', false); - } - $app['config']->set('graphql.schemas.default', [ 'query' => [ 'examples' => ExamplesQuery::class, @@ -55,7 +49,6 @@ protected function getEnvironmentSetUp($app): void 'examplesMiddleware' => ExamplesMiddlewareQuery::class, 'examplesPagination' => ExamplesPaginationQuery::class, 'examplesFiltered' => ExamplesFilteredQuery::class, - 'examplesConfigAlias' => ExamplesConfigAliasQuery::class, ], 'mutation' => [ 'updateExample' => UpdateExampleMutation::class, @@ -75,7 +68,6 @@ protected function getEnvironmentSetUp($app): void $app['config']->set('graphql.types', [ 'Example' => ExampleType::class, - 'ExampleConfigAlias' => ExampleType2::class, 'ExampleFilterInput' => ExampleFilterInputType::class, ]); @@ -203,15 +195,17 @@ protected function httpGraphql(string $query, array $options = []): array if (!$expectErrors && isset($result['errors'])) { $appendErrors = ''; - if (isset($result['errors'][0]['trace'])) { - $appendErrors = "\n\n" . $this->formatSafeTrace($result['errors'][0]['trace']); + if (isset($result['errors'][0]['extensions']['trace'])) { + $appendErrors = "\n\n" . $this->formatSafeTrace($result['errors'][0]['extensions']['trace']); } $assertMessage = "Probably unexpected error in GraphQL response:\n" . var_export($result, true) . $appendErrors; } - unset($result['errors'][0]['trace']); + unset($result['errors'][0]['extensions']['trace']); + unset($result['errors'][0]['extensions']['file']); + unset($result['errors'][0]['extensions']['line']); if ($assertMessage) { throw new ExpectationFailedException($assertMessage); diff --git a/tests/Unit/AutomatedPersistedQueriesTest.php b/tests/Unit/AutomatedPersistedQueriesTest.php index 407e58f1..0791c497 100644 --- a/tests/Unit/AutomatedPersistedQueriesTest.php +++ b/tests/Unit/AutomatedPersistedQueriesTest.php @@ -49,6 +49,9 @@ public function testPersistedQueryNotSupported(): void $content = $response->json(); + unset($content['errors'][0]['extensions']['file']); + unset($content['errors'][0]['extensions']['line']); + self::assertEquals([ 'errors' => [ [ @@ -77,6 +80,9 @@ public function testPersistedQueryNotFound(): void $content = $response->json(); + unset($content['errors'][0]['extensions']['file']); + unset($content['errors'][0]['extensions']['line']); + self::assertEquals([ 'errors' => [ [ @@ -200,6 +206,9 @@ public function testPersistedQueryInvalidHash(): void $content = $response->json(); + unset($content['errors'][0]['extensions']['file']); + unset($content['errors'][0]['extensions']['line']); + self::assertEquals([ 'errors' => [ [ @@ -244,6 +253,11 @@ public function testPersistedQueryBatchingNotSupported(): void $content = $response->json(); + unset($content[0]['errors'][0]['extensions']['file']); + unset($content[0]['errors'][0]['extensions']['line']); + unset($content[1]['errors'][0]['extensions']['file']); + unset($content[1]['errors'][0]['extensions']['line']); + self::assertArrayHasKey(0, $content); self::assertArrayHasKey(1, $content); @@ -329,6 +343,11 @@ public function testPersistedQueryBatchingFoundNotFoundAndInvalidHash(): void $content = $response->json(); + unset($content[1]['errors'][0]['extensions']['file']); + unset($content[1]['errors'][0]['extensions']['line']); + unset($content[2]['errors'][0]['extensions']['file']); + unset($content[2]['errors'][0]['extensions']['line']); + self::assertArrayHasKey(0, $content); self::assertArrayHasKey(1, $content); self::assertArrayHasKey(2, $content); @@ -452,14 +471,15 @@ public function testPersistedQueryNotAnArray(): void $content = $response->json(); - unset($content['errors'][0]['trace']); + unset($content['errors'][0]['extensions']['file']); + unset($content['errors'][0]['extensions']['line']); + unset($content['errors'][0]['extensions']['trace']); $expected = [ 'errors' => [ [ 'message' => 'GraphQL Request must include at least one of those two parameters: "query" or "queryId"', 'extensions' => [ - 'category' => 'request', ], ], ], @@ -485,12 +505,14 @@ public function testPersistedQueryParseError(): void $content = $response->json(); + unset($content['errors'][0]['extensions']['file']); + unset($content['errors'][0]['extensions']['line']); + $expected = [ 'errors' => [ [ 'message' => 'Syntax Error: Expected :, found )', 'extensions' => [ - 'category' => 'graphql', ], 'locations' => [ [ diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index dc6e3c0f..82dea397 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -72,11 +72,11 @@ public function testSchema(): void public function testSecurity(): void { /** @var QueryComplexity $queryComplexity */ - $queryComplexity = DocumentValidator::getRule('QueryComplexity'); + $queryComplexity = DocumentValidator::getRule(QueryComplexity::class); self::assertEquals(1000, $queryComplexity->getMaxQueryComplexity()); /** @var QueryDepth $queryDepth */ - $queryDepth = DocumentValidator::getRule('QueryDepth'); + $queryDepth = DocumentValidator::getRule(QueryDepth::class); self::assertEquals(10, $queryDepth->getMaxQueryDepth()); } diff --git a/tests/Unit/EndpointTest.php b/tests/Unit/EndpointTest.php index 007a6605..5e6aec14 100644 --- a/tests/Unit/EndpointTest.php +++ b/tests/Unit/EndpointTest.php @@ -159,14 +159,16 @@ public function testBatchedQueriesDontWorkWithGet(): void self::assertEquals(200, $response->getStatusCode()); $content = $response->getData(true); - unset($content['errors'][0]['trace']); + + unset($content['errors'][0]['extensions']['file']); + unset($content['errors'][0]['extensions']['line']); + unset($content['errors'][0]['extensions']['trace']); $expected = [ 'errors' => [ [ 'message' => 'GraphQL Request must include at least one of those two parameters: "query" or "queryId"', 'extensions' => [ - 'category' => 'request', ], ], ], diff --git a/tests/Unit/EngineErrorInResolverTests/EngineErrorInResolverTest.php b/tests/Unit/EngineErrorInResolverTests/EngineErrorInResolverTest.php index bee45bb2..dac07b8c 100644 --- a/tests/Unit/EngineErrorInResolverTests/EngineErrorInResolverTest.php +++ b/tests/Unit/EngineErrorInResolverTests/EngineErrorInResolverTest.php @@ -28,7 +28,7 @@ public function testForEngineError(): void ]); // Using a regex here because in some cases the message gets prefixed with "Type error:" - self::assertMatchesRegularExpression('/Simulating a TypeError/', $result['errors'][0]['debugMessage']); + self::assertMatchesRegularExpression('/Simulating a TypeError/', $result['errors'][0]['extensions']['debugMessage']); } protected function resolveApplicationExceptionHandler($app): void diff --git a/tests/Unit/GraphQLQueryTest.php b/tests/Unit/GraphQLQueryTest.php index 7e0f5f0e..9a7d843a 100644 --- a/tests/Unit/GraphQLQueryTest.php +++ b/tests/Unit/GraphQLQueryTest.php @@ -21,36 +21,6 @@ public function testQueryAndReturnResult(): void ]); } - public function testConfigKeysIsDifferentFromTypeClassNameQuery(): void - { - if (app('config')->get('graphql.lazyload_types')) { - self::markTestSkipped('Skipping test when lazyload_types=true'); - } - - $result = GraphQL::queryAndReturnResult($this->queries['examplesWithConfigAlias']); - - self::assertObjectHasAttribute('data', $result); - - self::assertEquals($result->data, [ - 'examplesConfigAlias' => $this->data, - ]); - } - - public function testConfigKeyIsDifferentFromTypeClassNameNotSupportedInLazyLoadingOfTypes(): void - { - if (false === app('config')->get('graphql.lazyload_types')) { - self::markTestSkipped('Skipping test when lazyload_types=false'); - } - - $result = GraphQL::queryAndReturnResult($this->queries['examplesWithConfigAlias']); - self::assertObjectHasAttribute('errors', $result); - - $expected = "Type Example2 not found. -Check that the config array key for the type matches the name attribute in the type's class. -It is required when 'lazyload_types' is enabled"; - self::assertSame($expected, $result->errors[0]->getMessage()); - } - public function testQuery(): void { $resultArray = GraphQL::query($this->queries['examples']); diff --git a/tests/Unit/GraphQLTest.php b/tests/Unit/GraphQLTest.php index 92aa554e..9859ee6f 100644 --- a/tests/Unit/GraphQLTest.php +++ b/tests/Unit/GraphQLTest.php @@ -126,11 +126,11 @@ public function testListOfNonNullType(): void /** @var ListOfType */ $typeOther = GraphQL::type('[Example!]'); - self::assertSame($type->getWrappedType(true), $typeOther->getWrappedType(true)); + self::assertSame($type->getInnermostType(), $typeOther->getInnermostType()); /** @var ListOfType */ $typeOther = GraphQL::type('[Example!]', true); - self::assertNotSame($type->getWrappedType(true), $typeOther->getWrappedType(true)); + self::assertNotSame($type->getInnermostType(), $typeOther->getInnermostType()); } public function testNonNullListOfNonNullType(): void @@ -146,11 +146,11 @@ public function testNonNullListOfNonNullType(): void /** @var NonNull */ $typeOther = GraphQL::type('[Example!]!'); - self::assertSame($type->getWrappedType(true), $typeOther->getWrappedType(true)); + self::assertSame($type->getInnermostType(), $typeOther->getInnermostType()); /** @var NonNull */ $typeOther = GraphQL::type('[Example!]!', true); - self::assertNotSame($type->getWrappedType(true), $typeOther->getWrappedType(true)); + self::assertNotSame($type->getInnermostType(), $typeOther->getInnermostType()); } public function testMalformedListOfWithNoLeadingBracket(): void @@ -226,7 +226,7 @@ public function testStandardTypeModifiers(): void self::assertInstanceOf(ListOfType::class, $type); self::assertInstanceOf(NonNull::class, $type->getWrappedType()); - self::assertSame($type->getWrappedType(true), $standardType); + self::assertSame($type->getInnermostType(), $standardType); /** @var NonNull */ $type = GraphQL::type("[$standardType->name!]!"); @@ -236,7 +236,7 @@ public function testStandardTypeModifiers(): void self::assertInstanceOf(NonNull::class, $type); self::assertInstanceOf(ListOfType::class, $wrappedType); self::assertInstanceOf(NonNull::class, $wrappedType->getWrappedType()); - self::assertSame($type->getWrappedType(true), $standardType); + self::assertSame($type->getInnermostType(), $standardType); } } @@ -288,19 +288,22 @@ public function testFormatError(): void $result = GraphQL::queryAndReturnResult($this->queries['examplesWithError']); $error = GraphQL::formatError($result->errors[0]); + unset($error['extensions']['file']); + unset($error['extensions']['line']); + + self::assertIsArray($error); self::assertArrayHasKey('message', $error); self::assertArrayHasKey('locations', $error); $expectedError = [ - 'message' => 'Cannot query field "examplesQueryNotFound" on type "Query".', - 'extensions' => [ - 'category' => 'graphql', - ], + 'message' => 'Cannot query field "examplesQueryNotFound" on type "Query". Did you mean "examplesPagination"?', 'locations' => [ [ 'line' => 3, 'column' => 13, ], ], + 'extensions' => [ + ], ]; self::assertEquals($expectedError, $error); } @@ -315,8 +318,13 @@ public function testFormatValidationError(): void $error = new Error('error', null, null, [], null, $validationError); $error = GraphQL::formatError($error); - self::assertArrayHasKey('trace', $error); - unset($error['trace']); + self::assertArrayHasKey('extensions', $error); + self::assertArrayHasKey('file', $error['extensions']); + self::assertArrayHasKey('line', $error['extensions']); + self::assertArrayHasKey('trace', $error['extensions']); + unset($error['extensions']['file']); + unset($error['extensions']['line']); + unset($error['extensions']['trace']); $expected = [ 'message' => 'error', diff --git a/tests/Unit/IntrospectionCanBeDisabledTest.php b/tests/Unit/IntrospectionCanBeDisabledTest.php index 34b4d8fc..2db5a8f7 100644 --- a/tests/Unit/IntrospectionCanBeDisabledTest.php +++ b/tests/Unit/IntrospectionCanBeDisabledTest.php @@ -27,9 +27,6 @@ public function testIntrospectionCanBeDisabled(): void 'errors' => [ [ 'message' => 'GraphQL introspection is not allowed, but the query contained __schema or __type', - 'extensions' => [ - 'category' => 'graphql', - ], 'locations' => [ [ 'line' => 2, diff --git a/tests/Unit/TypesInSchemas/QueriesAndTypesEachInTheirOwnSchemaTest.php b/tests/Unit/TypesInSchemas/QueriesAndTypesEachInTheirOwnSchemaTest.php index d9dbd886..2c1d937c 100644 --- a/tests/Unit/TypesInSchemas/QueriesAndTypesEachInTheirOwnSchemaTest.php +++ b/tests/Unit/TypesInSchemas/QueriesAndTypesEachInTheirOwnSchemaTest.php @@ -25,11 +25,6 @@ protected function getEnvironmentSetUp($app): void SchemaTwo\Type::class, ], ]); - - // To still properly support dual tests, we thus have to add this - if ('0' === env('TESTS_ENABLE_LAZYLOAD_TYPES')) { - $app['config']->set('graphql.lazyload_types', false); - } } public function testQueriesAndTypesEachInTheirOwnSchema(): void diff --git a/tests/Unit/TypesInSchemas/TypesTest.php b/tests/Unit/TypesInSchemas/TypesTest.php index 9df8526d..5a372fca 100644 --- a/tests/Unit/TypesInSchemas/TypesTest.php +++ b/tests/Unit/TypesInSchemas/TypesTest.php @@ -11,11 +11,6 @@ class TypesTest extends TestCase protected function getEnvironmentSetUp($app): void { // Note: deliberately not calling parent to start with a clean config - - // To still properly support dual tests, we thus have to add this - if ('0' === env('TESTS_ENABLE_LAZYLOAD_TYPES')) { - $app['config']->set('graphql.lazyload_types', false); - } } public function testQueryAndTypeInDefaultSchema(): void @@ -107,9 +102,6 @@ public function testQueryAndTypeInCustomSchemaQueryingDefaultSchema(): void 'errors' => [ [ 'message' => 'Cannot query field "query" on type "Query".', - 'extensions' => [ - 'category' => 'graphql', - ], 'locations' => [ [ 'line' => 2, @@ -292,9 +284,6 @@ public function testDifferentQueriesInDifferentSchemasAndTypeGlobal(): void 'errors' => [ [ 'message' => 'Cannot query field "title" on type "Type".', - 'extensions' => [ - 'category' => 'graphql', - ], 'locations' => [ [ 'line' => 3, diff --git a/tests/Unit/UnusedVariablesTest.php b/tests/Unit/UnusedVariablesTest.php index 069c4c0c..bf24d4bd 100644 --- a/tests/Unit/UnusedVariablesTest.php +++ b/tests/Unit/UnusedVariablesTest.php @@ -56,12 +56,14 @@ public function testFeatureEnabledUnusedVariableThrowsError(): void $content = $response->getData(true); + unset($content['errors'][0]['extensions']['file']); + unset($content['errors'][0]['extensions']['line']); + $expected = [ 'errors' => [ [ 'message' => 'The following variables were provided but not consumed: unused_variable, another_unused_variable', 'extensions' => [ - 'category' => 'graphql', ], ], ],