Skip to content

Commit

Permalink
Cursor pagination support (#917)
Browse files Browse the repository at this point in the history
* Ability to add cursor pagination

Extend model pagination with cursor pagination.

```
@apiResourceModel App\Models\User paginate=10,cursor
```

* Add tests for cursor pagination

* fix tests on older laravel versions

* fix per_page

---------

Co-authored-by: fikri-kompanion <[email protected]>
  • Loading branch information
olivernybroe and fikri-kompanion authored Nov 20, 2024
1 parent 018cac9 commit 46e8399
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Attributes/ResponseFromApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function __construct(

public ?int $paginate = null,
public ?int $simplePaginate = null,
public ?int $cursorPaginate = null,
public array $additional = [],
)
{
Expand Down
5 changes: 5 additions & 0 deletions src/Extracting/Shared/ApiResourceResponseTools.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Pagination\CursorPaginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Arr;
Expand Down Expand Up @@ -86,6 +87,10 @@ public static function getApiResourceOrCollectionInstance(
$perPage = $paginationStrategy[0];
$paginator = new Paginator($models, $perPage);
$list = $paginator;
} elseif (count($paginationStrategy) == 2 && $paginationStrategy[1] == 'cursor') {
$perPage = $paginationStrategy[0];
$paginator = new CursorPaginator($models, $perPage);
$list = $paginator;
} else {
$list = collect($models);
}
Expand Down
2 changes: 2 additions & 0 deletions src/Extracting/Strategies/Responses/UseResponseAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ protected function getApiResourceResponse(ResponseFromApiResource $attributeInst
$pagination = [$attributeInstance->paginate];
} else if ($attributeInstance->simplePaginate) {
$pagination = [$attributeInstance->simplePaginate, 'simple'];
} else if ($attributeInstance->cursorPaginate) {
$pagination = [$attributeInstance->cursorPaginate, 'cursor'];
}


Expand Down
51 changes: 51 additions & 0 deletions tests/Strategies/Responses/UseApiResourceTagsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Knuckles\Scribe\Tests\Strategies\Responses;

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Application;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\Schema;
use Knuckles\Camel\Extraction\ExtractedEndpointData;
Expand Down Expand Up @@ -798,4 +799,54 @@ public function can_parse_apiresourcecollection_tags_with_collection_class_pagin
],
], $results);
}

/** @test */
public function can_parse_apiresourcecollection_tags_with_collection_class_and_cursor_pagination()
{
$config = new DocumentationConfig([]);

$route = new Route(['POST'], "/somethingRandom", ['uses' => [TestController::class, 'dummy']]);

$strategy = new UseApiResourceTags($config);
$tags = [
new Tag('apiResourceCollection', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResourceCollection'),
new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser paginate=1,cursor'),
];
$results = $strategy->getApiResourceResponseFromTags($strategy->getApiResourceTag($tags), $tags, ExtractedEndpointData::fromRoute($route));

$nextCursor = base64_encode(json_encode(['_pointsToNextItems' => true]));
$this->assertArraySubset([
[
'status' => 200,
'content' => json_encode([
'data' => [
[
'id' => 4,
'name' => 'Tested Again',
'email' => '[email protected]',
],
],
'links' => [
'self' => 'link-value',
"first" => null,
"last" => null,
"prev" => null,
"next" => "/?cursor={$nextCursor}",
],
"meta" => match (version_compare(Application::VERSION, '9.0', '>=')) {
false => [
"path" => '/',
'per_page' => '1',
],
true => [
"path" => '/',
'per_page' => 1,
'next_cursor' => $nextCursor,
'prev_cursor' => null,
]
},
]),
],
], $results);
}
}
63 changes: 63 additions & 0 deletions tests/Strategies/Responses/UseResponseAttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Knuckles\Scribe\Tests\Strategies\Responses;

use Illuminate\Foundation\Application;
use Illuminate\Routing\Route;
use Knuckles\Camel\Extraction\ExtractedEndpointData;
use Knuckles\Scribe\Attributes\Response;
Expand Down Expand Up @@ -228,6 +229,62 @@ public function can_parse_transformer_attributes()
], $results);
}

/** @test */
public function can_parse_apiresource_attributes_with_cursor_pagination()
{
$factory = app(\Illuminate\Database\Eloquent\Factory::class);
$factory->afterMaking(TestUser::class, function (TestUser $user, $faker) {
if ($user->id === 4) {
$child = Utils::getModelFactory(TestUser::class)->make(['id' => 5, 'parent_id' => 4]);
$user->setRelation('children', collect([$child]));
}
});

$results = $this->fetch($this->endpoint("apiResourceAttributesWithCursorPaginate"));


$nextCursor = base64_encode(json_encode(['_pointsToNextItems' => true]));
$this->assertArraySubset([
[
'status' => 200,
'content' => json_encode([
'data' => [
[
'id' => 4,
'name' => 'Tested Again',
'email' => '[email protected]',
'children' => [
[
'id' => 5,
'name' => 'Tested Again',
'email' => '[email protected]',
],
],
],
],
'links' => [
"first" => null,
"last" => null,
"prev" => null,
"next" => "/?cursor={$nextCursor}",
],
"meta" => match (version_compare(Application::VERSION, '9.0', '>=')) {
false => [
"path" => '/',
'per_page' => 1,
],
true => [
"path" => '/',
'per_page' => 1,
'next_cursor' => $nextCursor,
'prev_cursor' => null,
]
},
]),
],
], $results);
}

protected function fetch($endpoint): array
{
$strategy = new UseResponseAttributes(new DocumentationConfig([]));
Expand Down Expand Up @@ -282,4 +339,10 @@ public function transformerAttributes()
{

}

#[ResponseFromApiResource(TestUserApiResource::class, collection: true, cursorPaginate: 1)]
public function apiResourceAttributesWithCursorPaginate()
{

}
}

0 comments on commit 46e8399

Please sign in to comment.