Response Body Returning String when using a ResponseTrait
Hi - We use a custom Response Trait to organise all our responses. When using this, we get the following on our Docs:
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 thank you! That's enough info, and I will fix it
Amazing, thanks @romalytvynenko !
Hi @romalytvynenko Did a fix for this get released?
@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 Just tested this, Still the same results as the original post.
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.');
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.
I'm also facing the same issue. Any updates on this one?
hi, Same issue here ? No solutions ? Thanks a lot
@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]