dedoc / scramble

Modern Laravel OpenAPI (Swagger) documentation generator. No PHPDoc annotations required.
https://scramble.dedoc.co/
MIT License
1.15k stars 108 forks source link

Response Body Returning String when using ApiResponser Trait #540

Open arislanhaikal opened 6 days ago

arislanhaikal commented 6 days ago

Hello All I use scamble version 0.11.13 with Laravel 11. When I build an API, I always create an ApiResponser Trait to keep my API response consistent.

Such as:

{
    success: boolean,
    data: array,
    message: string
}

I create ApiResponser.php trait

<?php

namespace App\Traits;

use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;

trait ApiResponser
{
    /**
     * Building success response
     *
     * @param  string|array  $data
     * @param  ?string  $message
     * @param  int  $code
     * @return JsonResponse
     */
    public function successResponse($data, $message = null, $code = Response::HTTP_OK)
    {
        return response()->json([
            'success' => true,
            'data' => $data,
            'message' => $message,
        ], $code);
    }
}

and then I used in Controller.php

<?php

namespace App\Http\Controllers;

use App\Traits\ApiResponser;

abstract class Controller
{
    use ApiResponser;
}

In another controller, example AuthController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    /**
     * Login
     *
     * @unauthenticated
     *
     * @throws ValidationException
     */
    public function login(LoginRequest $request): JsonResponse
    {
        // LOGIC

        return $this->successResponse([
            'token' => 'blablabla',
            'token_type' => 'Bearer',
        ], __('Login successful'));
    }
}

The expected Response Body in scramble is

{
  "success": boolean,
  "data": {
    "token": string,
    "token_type": string
  },
  "message": string
}

But response body in scramble is string 000210-wAhUYmRP@2x

Is there something wrong with me? Or how should I return API responses with a consistent format? Thank you

romalytvynenko commented 5 days ago

@arislanhaikal This is Scramble's issue. I will fix!

valpuia604 commented 5 days ago
public function index(Requests $request): JsonResource
{
    // more code including `$user`
    return $this->returnData($user);
}

public function returnData($user): JsonResource
{
    // some more code
    return new UsersResource($user);
}

Above code is also returning the similar format (OP) in scramble. Just drop here just in case

Screenshot from 2024-09-13 11-11-15

aashirhaq commented 5 days ago

I am also facing the same issue for my response:

{ "error": false, "data": [ { "id": 1, "name": "USA", "slug": "usa" } ] }

romalytvynenko commented 5 days ago

@aashirhaq, I have no idea why. Please share some code examples. Otherwise I won't be able to help

romalytvynenko commented 5 days ago

@valpuia604 that code that you've excluded matters. First of all make sure you use the latest version. If it doesn't work, include enough code so I can reproduce.

aashirhaq commented 5 days ago

@romalytvynenko I am using dedoc/scramble v0.11.13

Here is my full code:

Controller file:

public function index(Request $request)
{
    $data = array();
    $error = false;
    $message = null;
    $params = $request->all();

    try {
        $records = $this->service->list($params);

        $data = PaymentMethod::collection($records);
    } catch (\Exception $e) {
        throw $e;
        Log::error($e);
    }
    return $this->responder->respond($error, $data, $message);
}

And, the respond method is below:

   public function respond($error = 500, $data = array(), $message = null)
{
    $responseData = array();
    if(!$message && $error)
        $message = $this->getMessage($error);
    $status = $error ? true : false;
    $responseData = [
        'error' => $status,
        'data' => array()
    ];
    if($error && $error === 422){
        $responseData['data']['errors'] = $data;
    }
    if($error){
        $responseData['data']['code'] = $error;
    }
    if($error && $message){
        $responseData['data']['message'] = $message;
    }
    if(!$status && !$error){
        $error = 200;
        $responseData['data'] = $data;
    }
    return response()->json($responseData, $error, [], JSON_UNESCAPED_SLASHES);
}

Here is PaymentMethod resource file code:

<?php

namespace App\Http\Resources;

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

class PaymentMethod extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'iD' => $this->id,
            'name' => $this->name,
            'slug' => $this->slug,
            'type' => $this->type,
            'gatewayType' => $this->gateway_type,
            'logoUrl' => $this->logo_uri,
        ];
    }
}

Response is Generating from above code is here:

{
    "error": false,
    "data": [
        {
            "iD": 49,
            "name": "Credit/Debit Card",
            "slug": "credit-debit-card",
            "type": "payment",
            "gatewayType": null,
            "logoUrl": "credit-debit-card-94257.png"
        }
    ]
}
valpuia604 commented 5 days ago

@valpuia604 that code that you've excluded matters. First of all make sure you use the latest version. If it doesn't work, include enough code so I can reproduce.

Hi I've updated to 0.11.13 and got the same output, below are how to reproduced

/**
* @unauthenticated
*/
public function index(LoginRequest $request): JsonResponse|JsonResource
{
    $user = User::where('username', $request->validated('username'))
        ->first();

    if (! $user || ! Hash::check($request->validated('password'), $user->password)) {
        return $this->returnError(__('auth.failed'));
    }

    return $this->loadUser($user);
}

/**
* Why not include in `index` instead of calling below method?
*
* Well I have another login method which check a little bit different
* So try to follow DRY
*/
public function loadUser($user): JsonResponse|JsonResource
{
    if (! $user->is_active) {
        return $this->returnError(__('messages.acc_disabled'));
    }

    $user['token'] = $user->createToken('auth_token')->plainTextToken;

    return new UsersResource($user);
}

public function returnError($messageDescription): JsonResponse
{
    return response()->json([
        'message' => [
            'title' => __('messages.error'),
            'description' => $messageDescription,
        ],
        'status' => false,
    ], 422);
}

// Login Request

public function rules(): array
{
    return [
        'username' => 'required',
        'password' => 'required',
    ];
}
romalytvynenko commented 5 days ago

@aashirhaq your example is not supported yet, as it is too complex: type is being built conditionally. For now, Scramble doesn't support that.

valpuia604 commented 3 hours ago

@valpuia604 that code that you've excluded matters. First of all make sure you use the latest version. If it doesn't work, include enough code so I can reproduce.

Hi I've updated to 0.11.13 and got the same output, below are how to reproduced

/**
* @unauthenticated
*/
public function index(LoginRequest $request): JsonResponse|JsonResource
{
    $user = User::where('username', $request->validated('username'))
        ->first();

    if (! $user || ! Hash::check($request->validated('password'), $user->password)) {
        return $this->returnError(__('auth.failed'));
    }

    return $this->loadUser($user);
}

/**
* Why not include in `index` instead of calling below method?
*
* Well I have another login method which check a little bit different
* So try to follow DRY
*/
public function loadUser($user): JsonResponse|JsonResource
{
    if (! $user->is_active) {
        return $this->returnError(__('messages.acc_disabled'));
    }

    $user['token'] = $user->createToken('auth_token')->plainTextToken;

    return new UsersResource($user);
}

public function returnError($messageDescription): JsonResponse
{
    return response()->json([
        'message' => [
            'title' => __('messages.error'),
            'description' => $messageDescription,
        ],
        'status' => false,
    ], 422);
}

// Login Request

public function rules(): array
{
    return [
        'username' => 'required',
        'password' => 'required',
    ];
}

Digging deeper and it's not about another method calls, it's only return type JsonResponse|JsonResource If I didn't specify the return type it's working fine