scramble icon indicating copy to clipboard operation
scramble copied to clipboard

Response Body Returning String when using a ResponseTrait

Open jcsix694 opened this issue 2 years ago • 9 comments

Hi - We use a custom Response Trait to organise all our responses. When using this, we get the following on our Docs: image

This, particular endpoint returns the following format:

{
  "status": 200,
  "message": "Returned games",
  "data": [
    {
      "uuid": "abcc54f2-f16f-4266-8516-1a79c63f54b7",
      "name": "TestABC,
      "system": "PC",
      "images": {
        "logo": null
      }
    },
    {
      "uuid": "4bae2704-6b51-47e2-816a-feabc790fa8c",
      "name": "Test123",
      "system": "PC",
      "images": {
        "logo": null
      }
    },
  ]
}

So the code, We have a Game Resource which is the follwing

<?php

namespace App\Api\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

/**
 * @property string $name
 * @property string $uuid
 * @property string $system
 * @property boolean $image_logo
 */
class GameResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  Request  $request
     * @return array
     */
    public function toArray($request): array {
        if ($this->image_logo) {
            $imageUrlLogo = env('AWS_S3_URL') . '/' . env('AWS_BUCKET') . '/' . env('APP_ENV') . '/games/' . $this->uuid . '/logo.webp';
        } else {
            $imageUrlLogo = null;
        }

        return [
            'uuid' => $this->uuid,
            'name' => $this->name,
            'system' => $this->system,
            'images' => [
                'logo' => $imageUrlLogo,
            ]
        ];
    }
}

This, is the controller

    /**
     * Returns a list of games
     *
     *
     * @param GetGameRequest $request
     * @return JsonResponse
     */
    public function getGames(GetGameRequest $request): JsonResponse {
        try {
            $games = $this->gamesRepository->getGames($request);

            $gameResources = $games->map(function ($game) {
                return new GameResource($game);
            });

            return $this->success('Returned games', $gameResources, StatusCodeHelper::STATUS_OK);
        } catch (Throwable $e) {
            return $this->error($e->getMessage(), $e->getCode());
        }
    }

$this->success and $this->error go to the ResponseTrait. We'll take $this->success code:

    /**
     * Returns json success.
     *
     * @param string|null $message
     * @param object|null $data
     * @param int|null $statusCode
     * @return JsonResponse
     */
    public function success(string $message = null, object $data = null, int $statusCode = null): JsonResponse {
        if(!$statusCode) $statusCode = StatusCodeHelper::STATUS_OK;
        return $this->jsonResponse($statusCode, $data, $message);
    }

Which then goes to the actual response

/**
     * Returns json response.
     *
     * @param int $statusCode
     * @param object|array|null $data
     * @param string|null $messages
     * @param array|null $meta
     * @return JsonResponse
     */
    public function jsonResponse(int $statusCode, object|array $data = null, string $messages = null, PaginationResource $meta = null): JsonResponse {
        $array = array(
            'status' => $statusCode,
            'message' => $messages
        );

        if($data) $array['data'] = $data;
        if($meta) $array['meta'] = $meta;

        return response()->json($array, $statusCode, [], JSON_UNESCAPED_SLASHES);
    }

I was told on discord to put in this issue. Is there a potential fix for this without me, having to redo all the responses?

jcsix694 avatar Aug 21 '23 13:08 jcsix694

@jcsix694 thank you! That's enough info, and I will fix it

romalytvynenko avatar Aug 21 '23 13:08 romalytvynenko

Amazing, thanks @romalytvynenko !

jcsix694 avatar Aug 21 '23 14:08 jcsix694

Hi @romalytvynenko Did a fix for this get released?

jcsix694 avatar Dec 06 '23 10:12 jcsix694

@jcsix694 good question. Can you try 0.8.5 and check if it works? A lot has been done so this can actually work, but I'm not sure.

romalytvynenko avatar Dec 06 '23 11:12 romalytvynenko

@romalytvynenko Just tested this, Still the same results as the original post.

jcsix694 avatar Dec 10 '23 10:12 jcsix694

on version 0.8.5 too, this issue persists... also tried to put the following above the return statement but it is ignored entirely :(

        /**
         * @status 200
         * @body array{code:30100, status: 'success', message: 'The operation is performed successfully.', data: array{access_token: '48|txxxxxxxxxxxxxxx', token_type: 'Bearer', scope: 'website'}}
         */
        return $this->success([
            'access_token' => $token,
            'token_type' => 'Bearer'
        ], 'A new token is created.');

sprklinginfo avatar Dec 19 '23 01:12 sprklinginfo

BTW, Since it doesn't work for the trait, I also tried another technique by creating class ApiSuccessResponse implements Responsable, unfortunately it doesn't work for Illuminate\Contracts\Support\Responsable either.

sprklinginfo avatar Dec 27 '23 16:12 sprklinginfo

I'm also facing the same issue. Any updates on this one?

igorsgm avatar Feb 15 '24 06:02 igorsgm

hi, Same issue here ? No solutions ? Thanks a lot

yoan1005 avatar Feb 23 '24 10:02 yoan1005

@jcsix694 hey. Sorry for late reply.

While I've substantially improved type inference since you've created the issue, I can see that the real reason Scramble does not understand this specific case is null checks in the implementation.

Let's take this method for example.

public function jsonResponse(int $statusCode, object|array $data = null, string $messages = null, PaginationResource $meta = null): JsonResponse {
    $array = array(
        'status' => $statusCode,
        'message' => $messages
    );

    if($data) $array['data'] = $data;
    if($meta) $array['meta'] = $meta;

    return response()->json($array, $statusCode, [], JSON_UNESCAPED_SLASHES);
}

Its type signature can be expressed in Typescript-like syntax like following:

declare function jsonResponse<
  TCode extends int, 
  TData extends null|object|array, 
  TMessages extends null|string, 
  TMeta extends null|PaginationResource
>(
  TCode $statusCode, 
  TData $data = null, 
  TMessages $messages = null, 
  TMeta $meta = null
): TData extends null
  ? (
    TMeta extends null
      ? JsonResponse<array{status: TCode, message: TMessages}, ...>
      : JsonResponse<array{status: TCode, message: TMessages, meta: TMeta}, ...>
  )
  : (
    TMeta extends null
      ? JsonResponse<array{status: TCode, message: TMessages, data: TData}, ...>
      : JsonResponse<array{status: TCode, message: TMessages, data: TData, meta: TMeta}, ...>
  )

And currently it will be a monumental effort to enable Scramble to create SUCH branching types inference. Simpler way would be to infer this method's return type as JsonResponse<array{status: TCode, message: TMessages, data?: TData, meta?: TMeta}, ...> but I'm in doubt it will be really useful when making API documentation. But let me know if that makes sense.

Feel free to reach out to me via email so we can discuss Scramble needs for your API project: [email protected]

romalytvynenko avatar May 21 '24 08:05 romalytvynenko