scramble icon indicating copy to clipboard operation
scramble copied to clipboard

Wrong response structure for `$query->simplePaginate`

Open axelrindle opened this issue 1 year ago • 1 comments

I have the following controller function:

/**
 * Display a paginated list of News.
 *
 * @response LengthAwarePaginator<News>
 */
public function index(NewsListRequest $request): Paginator
{
    $query = News::query();

    if (Gate::check('news.viewall')) {
        $query = $query->withTrashed();
    }

    return $query->simplePaginate($request->query('perPage', 10));
}

Scramble generates the following response structure:

{
  "data": [
    {
      "id": 0,
      "created_at": "string",
      "updated_at": "string",
      "deleted_at": "string",
      "published_at": "string",
      "status": "active",
      "title": "string",
      "content": "string",
      "header_image": "string",
      "author": {
        "id": 0,
        "created_at": "2019-08-24T14:15:22Z",
        "updated_at": "2019-08-24T14:15:22Z",
        "name": "string",
        "email": "string",
        "avatar_url": "string",
        "status": "active"
      }
    }
  ],
  "links": {
    "first": "string",
    "last": "string",
    "prev": "string",
    "next": "string"
  },
  "meta": {
    "current_page": 0,
    "from": 0,
    "last_page": 0,
    "links": [
      {
        "url": "string",
        "label": "string",
        "active": true
      }
    ],
    "path": "string",
    "per_page": 0,
    "to": 0,
    "total": 0
  }
}

which is wrong and should in fact be:

{
  "current_page": 1,
  "data": [],
  "first_page_url": "http://localhost:8000/news?page=1",
  "from": null,
  "next_page_url": null,
  "path": "http://localhost:8000/news",
  "per_page": 10,
  "prev_page_url": null,
  "to": null
}

axelrindle avatar Jul 13 '24 15:07 axelrindle

I created the following extension to workaround the problem:

<?php

namespace App\OpenApi;

use Dedoc\Scramble\Extensions\TypeToSchemaExtension;
use Dedoc\Scramble\Support\Generator\Response;
use Dedoc\Scramble\Support\Generator\Schema;
use Dedoc\Scramble\Support\Generator\Types\ArrayType;
use Dedoc\Scramble\Support\Generator\Types\IntegerType;
use Dedoc\Scramble\Support\Generator\Types\ObjectType as OpenApiObjectType;
use Dedoc\Scramble\Support\Generator\Types\StringType;
use Dedoc\Scramble\Support\Type\Generic;
use Dedoc\Scramble\Support\Type\Type;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Log;

class SimplePaginationExtension extends TypeToSchemaExtension
{
    public function shouldHandle(Type $type): bool
    {
        if (! ($type instanceof Generic)) {
            return false;
        }

        return $type->name === Paginator::class;
    }

    public function toResponse(Type $type): Response
    {
        $collectingClassType = $type->templateTypes[0];

        if (! $collectingClassType->isInstanceOf(JsonResource::class) && ! $collectingClassType->isInstanceOf(Model::class)) {
            return null;
        }

        if (! ($collectingType = $this->openApiTransformer->transform($collectingClassType))) {
            return null;
        }

        $type = new OpenApiObjectType;
        $type->addProperty('current_page', new IntegerType);
        $type->addProperty('data', (new ArrayType())->setItems($collectingType));
        $type->addProperty('first_page_url', (new StringType)->nullable(true));
        $type->addProperty('from', (new IntegerType)->nullable(true));
        $type->addProperty('next_page_url', (new StringType)->nullable(true));
        $type->addProperty('path', (new StringType)->nullable(true)->setDescription('Base path for paginator generated URLs.'));
        $type->addProperty('prev_page_url', (new StringType)->nullable(true));
        $type->addProperty('to', (new IntegerType)->nullable(true)->setDescription('Number of the last item in the slice.'));
        $type->addProperty('per_page', (new IntegerType)->setDescription('Number of items shown per page.'));

        $type->setRequired(['data', 'current_page', 'first_page_url', 'path', 'per_page', 'from', 'to']);

        Log::info('schema', [Schema::fromType($type)]);

        return Response::make(200)
            ->description('Simple paginated set of `' . $this->components->uniqueSchemaName($collectingClassType->name) . '`')
            ->setContent('application/json', Schema::fromType($type));
    }
}

axelrindle avatar Jul 15 '24 07:07 axelrindle

@axelrindle fixed in 0.11.17!

romalytvynenko avatar Oct 13 '24 06:10 romalytvynenko