Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Response Body Returning String when using ApiResponser Trait #540

Open
arislanhaikal opened this issue Sep 12, 2024 · 14 comments
Open

Response Body Returning String when using ApiResponser Trait #540

arislanhaikal opened this issue Sep 12, 2024 · 14 comments

Comments

@arislanhaikal
Copy link

arislanhaikal commented Sep 12, 2024

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
Copy link
Member

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

@valpuia604
Copy link

valpuia604 commented Sep 13, 2024

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
Copy link

I am also facing the same issue for my response:

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

@romalytvynenko
Copy link
Member

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

@romalytvynenko
Copy link
Member

@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
Copy link

aashirhaq commented Sep 13, 2024

@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
Copy link

valpuia604 commented Sep 13, 2024

@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
Copy link
Member

@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
Copy link

valpuia604 commented Sep 18, 2024

@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

@arislanhaikal
Copy link
Author

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

Still not working inside trait method calls

@romalytvynenko
Copy link
Member

@aashirhaq Please create reproduction repository on GitHub. Otherwise, I won't be able to help.

@arislanhaikal
Copy link
Author

@aashirhaq Please create reproduction repository on GitHub. Otherwise, I won't be able to help.

Hi @romalytvynenko, i make simple example for this issue on github laravel-scramble-example . Can you check please!

@romalytvynenko
Copy link
Member

@aashirhaq Thanks. I'm on it, but it may take some time

@tegos
Copy link

tegos commented Nov 24, 2024

Hello, Roman,

I encountered a similar problem with rendering the response body as a string. However, there seems to be something mystical going on.
I use: v0.11.27

Code to Reproduce:

final class ApiResponse
{
    public static function json(
        mixed $data = [], 
        string $message = null, 
        $status = Response::HTTP_OK
    ): JsonResponse {
        $success = match (true) {
            $status >= 200 && $status < 300 => true,
            default => false,
        };

        return new JsonResponse([
            'success' => $success,
            'message' => $message,
            'data' => $data,
        ], $status);
    }
}

Observed Behavior

  • Without a type declaration for the $status argument, the code works as expected and produces the correct json response body.
    image

  • Issue: When I add a type declaration (int) to the $status argument, the response body is rendered as a string.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants