Skip to content

Commit

Permalink
pagination: enforce nonNull on the inner type as well as on the list
Browse files Browse the repository at this point in the history
  • Loading branch information
mfn committed May 29, 2023
1 parent a3484cf commit 7c0b5e0
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 11 deletions.
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
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(GraphQL::type('PostWithModel')->toString());
}

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

0 comments on commit 7c0b5e0

Please sign in to comment.