Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pagination: enforce nonNull on the inner type as well as on the list #1033

Merged
merged 1 commit into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ CHANGELOG
'Mutation' and 'Subscription'
- Return types were added to all methods of the commands [\#1005 / sforward](https://github.com/rebing/graphql-laravel/pull/1005)
- Upgrade to laragraph/utils v2 [\#1032 / mfn](https://github.com/rebing/graphql-laravel/pull/1032)
- The `Pagination` and `SimplePagination` helper types no enforce `nonNull` on their data types

### Removed
- Remove unused publish command [\#1004 / sforward](https://github.com/rebing/graphql-laravel/pull/1004)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1862,7 +1862,7 @@ class PostsQuery extends Query
{
public function type(): Type
{
return Type::nonNull(GraphQL::simplePaginate('posts'));
return GraphQL::simplePaginate('posts');
}

// ...
Expand Down
40 changes: 40 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ parameters:
count: 1
path: src/Support/PaginationType.php

-
message: "#^Parameter \\#1 \\$underlyingType of method Rebing\\\\GraphQL\\\\Support\\\\PaginationType\\:\\:getPaginationFields\\(\\) expects GraphQL\\\\Type\\\\Definition\\\\ObjectType, GraphQL\\\\Type\\\\Definition\\\\Type given\\.$#"
count: 1
path: src/Support/PaginationType.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Support\\\\Privacy\\:\\:fire\\(\\) has parameter \\$args with no type specified\\.$#"
count: 1
Expand Down Expand Up @@ -350,6 +355,11 @@ parameters:
count: 1
path: src/Support/SelectFields.php

-
message: "#^Parameter \\#1 \\$underlyingType of method Rebing\\\\GraphQL\\\\Support\\\\SimplePaginationType\\:\\:getPaginationFields\\(\\) expects GraphQL\\\\Type\\\\Definition\\\\ObjectType, GraphQL\\\\Type\\\\Definition\\\\Type given\\.$#"
count: 1
path: src/Support/SimplePaginationType.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Support\\\\Type\\:\\:getFieldResolver\\(\\) has parameter \\$field with no value type specified in iterable type array\\.$#"
count: 1
Expand Down Expand Up @@ -1005,6 +1015,36 @@ parameters:
count: 1
path: tests/Support/Objects/ExamplesQuery.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Queries\\\\PostNonNullPaginationQuery\\:\\:resolve\\(\\) has parameter \\$args with no type specified\\.$#"
count: 1
path: tests/Support/Queries/PostNonNullPaginationQuery.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Queries\\\\PostNonNullPaginationQuery\\:\\:resolve\\(\\) has parameter \\$context with no type specified\\.$#"
count: 1
path: tests/Support/Queries/PostNonNullPaginationQuery.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Queries\\\\PostNonNullPaginationQuery\\:\\:resolve\\(\\) has parameter \\$root with no type specified\\.$#"
count: 1
path: tests/Support/Queries/PostNonNullPaginationQuery.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Queries\\\\PostNonNullSimplePaginationQuery\\:\\:resolve\\(\\) has parameter \\$args with no type specified\\.$#"
count: 1
path: tests/Support/Queries/PostNonNullSimplePaginationQuery.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Queries\\\\PostNonNullSimplePaginationQuery\\:\\:resolve\\(\\) has parameter \\$context with no type specified\\.$#"
count: 1
path: tests/Support/Queries/PostNonNullSimplePaginationQuery.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Queries\\\\PostNonNullSimplePaginationQuery\\:\\:resolve\\(\\) has parameter \\$root with no type specified\\.$#"
count: 1
path: tests/Support/Queries/PostNonNullSimplePaginationQuery.php

-
message: "#^Method Rebing\\\\GraphQL\\\\Tests\\\\Support\\\\Queries\\\\PostNonNullWithSelectFieldsAndModelQuery\\:\\:resolve\\(\\) has no return type specified\\.$#"
count: 1
Expand Down
10 changes: 5 additions & 5 deletions src/Support/PaginationType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,25 @@ public function __construct(string $typeName, string $customName = null)
{
$name = $customName ?: $typeName . 'Pagination';

$underlyingType = GraphQL::type($typeName);

$config = [
'name' => $name,
'fields' => $this->getPaginationFields($typeName),
'fields' => $this->getPaginationFields($underlyingType),
];

$underlyingType = GraphQL::type($typeName);

if (isset($underlyingType->config['model'])) {
$config['model'] = $underlyingType->config['model'];
}

parent::__construct($config);
}

protected function getPaginationFields(string $typeName): array
protected function getPaginationFields(ObjectType $underlyingType): array
{
return [
'data' => [
'type' => GraphQLType::listOf(GraphQL::type($typeName)),
'type' => GraphQLType::nonNull(GraphQLType::listOf(GraphQLType::nonNull($underlyingType))),
'description' => 'List of items on the current page',
'resolve' => function (LengthAwarePaginator $data): Collection {
return $data->getCollection();
Expand Down
2 changes: 1 addition & 1 deletion src/Support/SelectFields.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ protected static function handleFields(
static::handleFields(
$queryArgs,
$field,
$fieldType->getWrappedType(),
$fieldType->getInnermostType(),
$select,
$with,
$ctx
Expand Down
10 changes: 5 additions & 5 deletions src/Support/SimplePaginationType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ public function __construct(string $typeName, string $customName = null)
{
$name = $customName ?: $typeName . 'SimplePagination';

$underlyingType = GraphQL::type($typeName);

$config = [
'name' => $name,
'fields' => $this->getPaginationFields($typeName),
'fields' => $this->getPaginationFields($underlyingType),
];

$underlyingType = GraphQL::type($typeName);

if (isset($underlyingType->config['model'])) {
$config['model'] = $underlyingType->config['model'];
}
Expand All @@ -32,11 +32,11 @@ public function __construct(string $typeName, string $customName = null)
/**
* @return array<string, array<string,mixed>>
*/
protected function getPaginationFields(string $typeName): array
protected function getPaginationFields(ObjectType $underlyingType): array
{
return [
'data' => [
'type' => GraphQLType::listOf(GraphQL::type($typeName)),
'type' => GraphQLType::nonNull(GraphQLType::listOf(GraphQLType::nonNull($underlyingType))),
'description' => 'List of items on the current page',
'resolve' => function (Paginator $data): Collection {
return $data->getCollection();
Expand Down
172 changes: 172 additions & 0 deletions tests/Database/SelectFieldsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
declare(strict_types = 1);
namespace Rebing\GraphQL\Tests\Database;

use GraphQL\Utils\SchemaPrinter;
use Illuminate\Support\Carbon;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Tests\Support\Models\Comment;
use Rebing\GraphQL\Tests\Support\Models\Post;
use Rebing\GraphQL\Tests\Support\Queries\PostNonNullPaginationQuery;
use Rebing\GraphQL\Tests\Support\Queries\PostNonNullSimplePaginationQuery;
use Rebing\GraphQL\Tests\Support\Queries\PostNonNullWithSelectFieldsAndModelQuery;
use Rebing\GraphQL\Tests\Support\Queries\PostQuery;
use Rebing\GraphQL\Tests\Support\Queries\PostQueryWithNonInjectableTypehintsQuery;
Expand Down Expand Up @@ -35,6 +39,8 @@ protected function getEnvironmentSetUp($app): void

$app['config']->set('graphql.schemas.default', [
'query' => [
PostNonNullPaginationQuery::class,
PostNonNullSimplePaginationQuery::class,
PostNonNullWithSelectFieldsAndModelQuery::class,
PostQuery::class,
PostsListOfWithSelectFieldsAndModelQuery::class,
Expand Down Expand Up @@ -614,4 +620,170 @@ public function testWithSelectFieldsNoModel(): void
self::assertEquals(200, $response->getStatusCode());
self::assertEquals($expectedResult, $response->json());
}
public function testPostNonNullPaginationQuery(): void
{
/** @var Post $post */
$post = factory(Post::class)->create([
'title' => 'Title of the post',
]);

$graphql = <<<'GRAQPHQL'
{
postNonNullPaginationQuery {
data {
id,
title,
}
}
}
GRAQPHQL;

$this->sqlCounterReset();

$response = $this->call('GET', '/graphql', [
'query' => $graphql,
]);

$this->assertSqlQueries(
<<<'SQL'
select count(*) as aggregate from "posts";
select "posts"."id", "posts"."title" from "posts" limit 15 offset 0;
SQL
);

$expectedResult = [
'data' => [
'postNonNullPaginationQuery' => [
'data' => [
[
'id' => "$post->id",
'title' => 'Title of the post',
],
],
],
],
];

self::assertEquals(200, $response->getStatusCode());
self::assertEquals($expectedResult, $response->json());
}

public function testPostNonNullPaginationTypes(): void
{
$schema = GraphQL::buildSchemaFromConfig([
'query' => [
'postNonNullPaginationQuery' => PostNonNullPaginationQuery::class,
],
]);

$gql = SchemaPrinter::doPrint($schema);

$queryFragment = <<<'GQL'
type PostWithModelPagination {
"List of items on the current page"
data: [PostWithModel!]!

"Number of total items selected by the query"
total: Int!

"Number of items returned per page"
per_page: Int!

"Current page of the cursor"
current_page: Int!

"Number of the first item returned"
from: Int

"Number of the last item returned"
to: Int

"The last page (number of pages)"
last_page: Int!

"Determines if cursor has more pages after the current page"
has_more_pages: Boolean!
}
GQL;

self::assertStringContainsString($queryFragment, $gql);
}

public function testPostNonNullSimplePaginationQuery(): void
{
/** @var Post $post */
$post = factory(Post::class)->create([
'title' => 'Title of the post',
]);

$graphql = <<<'GRAQPHQL'
{
postNonNullSimplePaginationQuery {
data {
id,
title,
}
}
}
GRAQPHQL;

$this->sqlCounterReset();

$response = $this->call('GET', '/graphql', [
'query' => $graphql,
]);

$this->assertSqlQueries('select "posts"."id", "posts"."title" from "posts" limit 16 offset 0;');

$expectedResult = [
'data' => [
'postNonNullSimplePaginationQuery' => [
'data' => [
[
'id' => "$post->id",
'title' => 'Title of the post',
],
],
],
],
];

self::assertEquals(200, $response->getStatusCode());
self::assertEquals($expectedResult, $response->json());
}

public function testPostNonNullSimplePaginationTypes(): void
{
$schema = GraphQL::buildSchemaFromConfig([
'query' => [
'postNonNullSimplePaginationQuery' => PostNonNullSimplePaginationQuery::class,
],
]);

$gql = SchemaPrinter::doPrint($schema);

$queryFragment = <<<'GQL'
type PostWithModelSimplePagination {
"List of items on the current page"
data: [PostWithModel!]!

"Number of items returned per page"
per_page: Int!

"Current page of the cursor"
current_page: Int!

"Number of the first item returned"
from: Int

"Number of the last item returned"
to: Int

"Determines if cursor has more pages after the current page"
has_more_pages: Boolean!
}
GQL;

self::assertStringContainsString($queryFragment, $gql);
}
}
31 changes: 31 additions & 0 deletions tests/Support/Queries/PostNonNullPaginationQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types = 1);
namespace Rebing\GraphQL\Tests\Support\Queries;

use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Tests\Support\Models\Post;

class PostNonNullPaginationQuery extends Query
{
protected $attributes = [
'name' => 'postNonNullPaginationQuery',
];

public function type(): Type
{
return GraphQL::paginate('PostWithModel');
}

public function resolve($root, $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields): LengthAwarePaginator
{
return Post::query()
->select($getSelectFields()->getSelect())
->paginate();
}
}
Loading
Loading