Skip to content

Commit 4340284

Browse files
committed
- Added API doc helpers
1 parent d8fb11e commit 4340284

File tree

6 files changed

+364
-16
lines changed

6 files changed

+364
-16
lines changed

composer.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
},
1717
"require-dev": {
1818
"orchestra/testbench": "^8.0 || ^9.0 || ^10.0",
19-
"phpunit/phpunit": "^10.5 || ^11.5.3"
19+
"phpunit/phpunit": "^10.5 || ^11.5.3",
20+
"knuckleswtf/scribe": "^5.3"
21+
},
22+
"suggest": {
23+
"knuckleswtf/scribe": "Required for API docs generation"
2024
},
2125
"autoload": {
2226
"psr-4": {

src/Concerns/ApiDocHelpers.php

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
<?php
2+
3+
namespace Javaabu\QueryBuilder\Concerns;
4+
5+
use Illuminate\Support\Str;
6+
use Spatie\QueryBuilder\AllowedFilter;
7+
8+
trait ApiDocHelpers
9+
{
10+
public static function apiDocDefaultSort(): string
11+
{
12+
/** @var self $new_instance */
13+
$new_instance = app(static::class);
14+
15+
return $new_instance->getDefaultSort();
16+
}
17+
18+
public static function apiDocAllowedFilters(): array
19+
{
20+
/** @var self $new_instance */
21+
$new_instance = app(static::class);
22+
23+
return $new_instance->getAllowedFilters();
24+
}
25+
26+
public static function apiDocAllowedIncludes(): array
27+
{
28+
/** @var self $new_instance */
29+
$new_instance = app(static::class);
30+
31+
return $new_instance->getAllowedIncludes();
32+
}
33+
34+
public static function apiDocIndexAllowedFields(): array
35+
{
36+
/** @var self $new_instance */
37+
$new_instance = app(static::class);
38+
39+
return array_merge($new_instance->getIndexAllowedFields(), $new_instance->getAllowedAppendAttributes());
40+
}
41+
42+
public static function apiDocIndexAllowedAppends(): array
43+
{
44+
/** @var self $new_instance */
45+
$new_instance = app(static::class);
46+
47+
return $new_instance->getAllowedAppendAttributes();
48+
}
49+
50+
public static function apiDocShowAllowedFields(): array
51+
{
52+
/** @var self $new_instance */
53+
$new_instance = app(static::class);
54+
55+
return array_merge($new_instance->getAllowedFields(), $new_instance->getShowAllowedAppendAttributes());
56+
}
57+
58+
public static function apiDocShowAllowedAppends(): array
59+
{
60+
/** @var self $new_instance */
61+
$new_instance = app(static::class);
62+
63+
return $new_instance->getShowAllowedAppendAttributes();
64+
}
65+
66+
public static function apiDocAllowedSorts(): array
67+
{
68+
/** @var self $new_instance */
69+
$new_instance = app(static::class);
70+
71+
return $new_instance->getAllowedSorts();
72+
}
73+
74+
public static function apiDocDefaultQueryParameters(
75+
array $fields = [],
76+
array $sorts = [],
77+
string $default_sort = '',
78+
array $appends = [],
79+
array $includes = [],
80+
array $filters = [],
81+
array $filter_metadata = []
82+
): array
83+
{
84+
$params = [];
85+
86+
if ($fields) {
87+
$params['fields'] = [
88+
'type' => 'string',
89+
'description' => 'Fields to include in the response. ' .
90+
'You can provide the fields as a comma-separated list, or provide the fields as an array query parameter i.e `fields[]`. ' .
91+
'By default all fields are included if the `fields` parameter is missing.'
92+
. '<br><br> **Allowed values:** ' . "\n" . implode("\n", array_map(fn($field) => "- `$field`", $fields)),
93+
'enum' => $fields,
94+
'example' => implode(',', $fields),
95+
];
96+
}
97+
98+
if ($sorts) {
99+
$params['sort'] = [
100+
'type' => 'string',
101+
'description' => 'Which fields to sort the results by. '.
102+
'<br>To sort in descending order, append a `-` to the field name, e.g. `?sort=-name`. '.
103+
'<br>To sort by multiple fields, provide a comma-separated list, e.g. `?sort=name,-created_at`. '.
104+
'<br><br>**Allowed sorts:** ' . "\n" . implode("\n", array_map(fn ($field) => "- `$field`", $sorts)) . "\n\n" .
105+
'<br>**Default sort:** ' . ($default_sort ? '`' . $default_sort . '`' : 'None'),
106+
'enum' => static::apiDocAllowedSorts(),
107+
'example' => static::apiDocDefaultSort(),
108+
];
109+
}
110+
111+
if ($appends) {
112+
$params['append'] = [
113+
'type' => 'string',
114+
'description' => 'Model accessor fields to include in the response. ' .
115+
'You can provide the append field as a comma-separated list, or provide the fields as an array query parameter i.e `append[]`. ' .
116+
'By default all appends are included if the `append` parameter is missing. ' .
117+
'To not append any fields, provide an empty string `?append=`'
118+
. '<br><br> **Allowed values:** ' . "\n" . implode("\n", array_map(fn($field) => "- `$field`", $appends)),
119+
'enum' => $appends,
120+
'example' => implode(',', $appends),
121+
];
122+
}
123+
124+
if ($includes) {
125+
$params['include'] = [
126+
'type' => 'string',
127+
'description' => 'Model relations to include in the response. ' .
128+
'You can provide the includes as a comma-separated list, or provide the includes as an array query parameter i.e `include[]`. ' .
129+
'By default all includes are included if the `include` parameter is missing. ' .
130+
'To not include any relations, provide an empty string `?include=`'
131+
. '<br><br> **Allowed values:** ' . "\n" . implode("\n", array_map(fn($field) => "- `$field`", $includes)),
132+
'enum' => $includes,
133+
'example' => implode(',', $includes),
134+
];
135+
}
136+
137+
$singular_resource_name = static::apiDocResourceNameSingularLower();
138+
139+
if ($filters) {
140+
foreach ($filters as $filter) {
141+
$filter_name = '';
142+
143+
if (is_string($filter)) {
144+
$filter_name = $filter;
145+
} elseif ($filter instanceof AllowedFilter) {
146+
$filter_name = $filter->getName();
147+
}
148+
149+
if (! $filter_name) {
150+
continue;
151+
}
152+
153+
$metadata = $filter_metadata[$filter_name] ?? [];
154+
155+
$params["filter[{$filter_name}]"] = array_merge([
156+
'type' => 'string',
157+
'description' => is_string($filter) ? 'Filter by the ' . Str::lower(slug_to_title($filter)) . ' of the ' . $singular_resource_name : 'Apply the ' . Str::lower(slug_to_title($filter_name)) . ' filter',
158+
], $metadata);
159+
}
160+
}
161+
162+
return $params;
163+
}
164+
165+
public static function apiDocDefaultIndexQueryParameters(): array
166+
{
167+
return static::apiDocDefaultQueryParameters(
168+
static::apiDocIndexAllowedFields(),
169+
static::apiDocAllowedSorts(),
170+
static::apiDocDefaultSort(),
171+
static::apiDocIndexAllowedAppends(),
172+
static::apiDocAllowedIncludes(),
173+
static::apiDocAllowedFilters(),
174+
static::apiDocFilterMetadata()
175+
);
176+
}
177+
178+
public static function apiDocDefaultShowQueryParameters(): array
179+
{
180+
return static::apiDocDefaultQueryParameters(
181+
fields: static::apiDocShowAllowedFields(),
182+
appends: static::apiDocShowAllowedAppends(),
183+
includes: static::apiDocAllowedIncludes()
184+
);
185+
}
186+
187+
public static function apiDocResourceName(): string
188+
{
189+
$class_name = Str::of(class_basename(static::class))
190+
->camel()
191+
->snake()
192+
->replace('_', ' ')
193+
->title()
194+
->toString();
195+
196+
if (Str::endsWith($class_name, 'Controller')) {
197+
$class_name = trim(Str::beforeLast($class_name, 'Controller'));
198+
}
199+
200+
return $class_name;
201+
}
202+
203+
public static function apiDocResourceNameSingular(): string
204+
{
205+
return Str::singular(static::apiDocResourceName());
206+
}
207+
208+
public static function apiDocResourceNameSingularLower(): string
209+
{
210+
return Str::lower(static::apiDocResourceNameSingular());
211+
}
212+
213+
public static function apiDocResourceNameLower(): string
214+
{
215+
return Str::lower(static::apiDocResourceName());
216+
}
217+
218+
public static function apiDocGroupMetadata(): array
219+
{
220+
return [
221+
'groupName' => static::apiDocGroupName(),
222+
'groupDescription' => static::apiDocGroupDescription(),
223+
];
224+
}
225+
226+
public static function apiDocGroupName(): string
227+
{
228+
return static::apiDocResourceName();
229+
}
230+
231+
public static function apiDocGroupDescription(): string
232+
{
233+
return 'Endpoints for listing and viewing ' . static::apiDocResourceNameLower();
234+
}
235+
236+
public static function apiDocIndexTitle(): string
237+
{
238+
return 'List all ' . static::apiDocResourceNameLower();
239+
}
240+
241+
public static function apiDocIndexDescription(): string
242+
{
243+
return 'Fetch all ' . static::apiDocResourceNameLower() . '. Supports filtering, sorting, pagination and field selection.';
244+
}
245+
246+
public static function apiDocShowTitle(): string
247+
{
248+
return 'View a single ' . static::apiDocResourceNameSingularLower();
249+
}
250+
251+
public static function apiDocShowDescription(): string
252+
{
253+
return 'Fetch a single ' . static::apiDocResourceNameSingularLower() . '. Supports field selection.';
254+
}
255+
256+
public static function apiDocFilterMetadata(): array
257+
{
258+
return [
259+
'id' => [
260+
'type' => 'integer',
261+
],
262+
263+
'search' => [
264+
'description' => 'Search ' . static::apiDocResourceNameLower() . '.',
265+
]
266+
];
267+
}
268+
269+
public static function apiDocIndexMetadata(): array
270+
{
271+
return [
272+
...static::apiDocGroupMetadata(),
273+
'title' => static::apiDocIndexTitle(),
274+
'description' => static::apiDocIndexDescription(),
275+
];
276+
}
277+
278+
public static function apiDocShowMetadata(): array
279+
{
280+
return [
281+
...static::apiDocGroupMetadata(),
282+
'title' => static::apiDocShowTitle(),
283+
'description' => static::apiDocShowDescription(),
284+
];
285+
}
286+
287+
public static function apiDocIndexQueryParameters(): array
288+
{
289+
return static::apiDocDefaultIndexQueryParameters();
290+
}
291+
292+
public static function apiDocShowQueryParameters(): array
293+
{
294+
return static::apiDocDefaultShowQueryParameters();
295+
}
296+
297+
public static function apiDocControllerMethodMetadata(string $method): array
298+
{
299+
return match ($method) {
300+
'index' => static::apiDocIndexMetadata(),
301+
'show' => static::apiDocShowMetadata(),
302+
default => static::apiDocGroupMetadata(),
303+
};
304+
}
305+
306+
public static function apiDocControllerMethodQueryParameters(string $method): array
307+
{
308+
return match ($method) {
309+
'index' => static::apiDocIndexQueryParameters(),
310+
'show' => static::apiDocShowQueryParameters(),
311+
default => [],
312+
};
313+
}
314+
}

src/Concerns/IsApiController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ trait IsApiController
2020
use AuthorizesRequests;
2121
use ValidatesRequests;
2222
use DispatchesJobs;
23+
use ApiDocHelpers;
2324

2425
/**
2526
* Query builder request

src/Http/Controllers/ApiController.php

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,11 @@
1111
abstract class ApiController extends ApiBaseController
1212
{
1313

14-
/**
15-
* Display a listing of the resource.
16-
*
17-
* @param Request $request
18-
* @return QueryBuilder[]|LengthAwarePaginator|\Illuminate\Database\Eloquent\Collection|\Spatie\QueryBuilder\QueryBuilder[]
19-
* @throws ValidationException
20-
*/
2114
public function index(Request $request)
2215
{
2316
return $this->indexEndpoint($request);
2417
}
2518

26-
/**
27-
* Display a single resource.
28-
*
29-
* @param $model_id
30-
* @param Request $request
31-
* @return \Illuminate\Database\Eloquent\Collection|Model|QueryBuilder|QueryBuilder[]|null
32-
* @throws ValidationException
33-
*/
3419
public function show($model_id, Request $request)
3520
{
3621
return $this->showEndpoint($model_id, $request);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Javaabu\QueryBuilder\Scribe\Strategies;
4+
5+
use Knuckles\Camel\Extraction\ExtractedEndpointData;
6+
use Knuckles\Scribe\Extracting\Strategies\Strategy;
7+
use Knuckles\Scribe\Tools\Utils as u;
8+
9+
class MetadataStrategy extends Strategy
10+
{
11+
12+
public function __invoke(ExtractedEndpointData $endpointData, array $settings = []): ?array
13+
{
14+
[$controller, $method] = u::getRouteClassAndMethodNames($endpointData->route);
15+
16+
if (method_exists($controller, 'apiDocControllerMethodMetadata') && ($metadata = $controller::apiDocControllerMethodMetadata($method))) {
17+
return $metadata;
18+
}
19+
20+
return null;
21+
}
22+
}

0 commit comments

Comments
 (0)