scribe icon indicating copy to clipboard operation
scribe copied to clipboard

Generate Response Fields from @apiResource or #[ResponseFromApiResource]

Open marfrede opened this issue 2 years ago • 2 comments

  • [x] I've read the documentation and I can't find details on how to achieve this.
  • [x] I almost know the section Responses by heart

I very much like the option to autogenerate an example response from a given API Resources and Models because it reduces code duplicity. But I would love if scribe would generate ResponseField (with name and type) from the API Resource / the Model (instead of just example responses).

In the following I describe an excerpt of my results so far and how I would like them to be:

  • I have
    • a model called Rating,
    • a route GET api/ratings and
    • a Controller Method RatingController::all() where a ResourceCollection (RatingCollection::class) is returned.
<?php
...
/**
 * Class Rating
 * 
 * @property int $id
 * @property Carbon|null $created_at
 * @property Carbon|null $updated_at
 * @property int $score
 * @property int $userID
 * @property int $bikeID
 * 
 * @property Bike $bike
 * @property User $user
 *
 * @package App\Models
 */
class Rating extends BaseModel
{
	protected $table = 'ratings';

	protected $casts = [
		'score' => 'int',
		'userID' => 'int',
		'bikeID' => 'int'
	];

	protected $fillable = [
		'score',
		'userID',
		'bikeID'
	];

	public function bike()
	{
		return $this->belongsTo(Bike::class, 'bikeID');
	}

	public function user()
	{
		return $this->belongsTo(User::class, 'userID');
	}
}
Route::get("ratings", [RatingController::class, 'all'])->name('ratings.get');
/**
 * @group Ratings 
 * API to create or update and read bike ratings
 */
class RatingController extends Controller
{
    /**
     * GET ratings
     * 
     * user sees a list of all ratings for all bikes (since a certain time)
     */
    #[QueryParam('sinceDate', 'date', 'time from which the ratings were submitted', required: false, example: '2023-04-26T12:30:00')]
    #[ResponseFromApiResource(RatingCollection::class, Rating::class)]
    public function all(FormRequests\Rating\GetAllRequest $request)
    {
        $validated = $request->validated();
        $ratings = Rating::where('updated_at', '>=', date("Y-m-d H:i:s", strtotime($validated['sinceDate'])))->get();
        return new JsonResponse(new RatingCollection($ratings), 200, [], 0);
    }
}

I get the following result: Screenshot 2023-01-04 at 01 14 59

I would love this result (without manually adding ResponseFields): Screenshot 2023-01-04 at 01 22 12

marfrede avatar Jan 04 '23 00:01 marfrede

I almost know the section Responses by heart

😆

I can't say much, because I'm very busy right now, but that sounds doable. PRs welcome. It's actually not so hard to do. Start with making a custom responseFields strategy that looks for an API resource response and generates fields from it. Creating the strategy isn't hard; the included response fields strategy already looks at the responses, to some degree.

The only difficulty might be in knowing which response came from an API resource. In which case there are two things you could do:

  1. don't bother where the response came from. If it's a 2xx, just extract all its fields.
  2. use the custom property on the EndpointData object to save the API resource response during the response phase, then read it during response fields phase.

shalvah avatar Jan 05 '23 18:01 shalvah

Now that I think of it, I'm not so certain I will accept this as a PR, sorry. I think I've thought about this before (option 1), but decided against it because I don't see the point of having a list of fields without any useful information beyond what's in the sample response. I think it could be a feature some folks want, but not others. It would be nice if there was a config option to enable/disable it, but strategies are not yet directly configurable. So, yeah, for now, I'd recommend a custom strategy.

shalvah avatar Jan 05 '23 18:01 shalvah