Skip to content

Commit 0e1418b

Browse files
authored
Merge pull request mpociot#416 from HydrefLab/feature/support-multiple-response-tags
Support for multiple response tags
2 parents b5ddfb0 + fb4ba04 commit 0e1418b

File tree

12 files changed

+364
-41
lines changed

12 files changed

+364
-41
lines changed

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,24 @@ public function show($id)
259259
}
260260
```
261261

262+
Moreover, you can define multiple `@response` tags as well as the HTTP status code related to a particular response (if no status code set, `200` will be returned):
263+
```php
264+
/**
265+
* @response {
266+
* "id": 4,
267+
* "name": "Jessica Jones",
268+
* "roles": ["admin"]
269+
* }
270+
* @response 404 {
271+
* "message": "No query results for model [\App\User]"
272+
* }
273+
*/
274+
public function show($id)
275+
{
276+
return User::findOrFail($id);
277+
}
278+
```
279+
262280
#### @transformer, @transformerCollection, and @transformerModel
263281
You can define the transformer that is used for the result of the route using the `@transformer` tag (or `@transformerCollection` if the route returns a list). The package will attempt to generate an instance of the model to be transformed using the following steps, stopping at the first successful one:
264282

@@ -308,7 +326,7 @@ composer require league/fractal
308326

309327
#### @responseFile
310328

311-
For large reponse bodies, you may want to use a dump of an actual response. You can put this response in a file (as a JSON string) within your Laravel storage directory and link to it. For instance, we can put this response in a file named `users.get.json` in `storage/responses`:
329+
For large response bodies, you may want to use a dump of an actual response. You can put this response in a file (as a JSON string) within your Laravel storage directory and link to it. For instance, we can put this response in a file named `users.get.json` in `storage/responses`:
312330

313331
```
314332
{"id":5,"name":"Jessica Jones","gender":"female"}
@@ -327,6 +345,18 @@ public function getUser(int $id)
327345
```
328346
The package will parse this response and display in the examples for this route.
329347

348+
Similarly to `@response` tag, you can provide multiple `@responseFile` tags along with the HTTP status code of the response:
349+
```php
350+
/**
351+
* @responseFile responses/users.get.json
352+
* @responseFile 404 responses/model.not.found.json
353+
*/
354+
public function getUser(int $id)
355+
{
356+
// ...
357+
}
358+
```
359+
330360
#### Generating responses automatically
331361
If you don't specify an example response using any of the above means, this package will attempt to get a sample response by making a request to the route (a "response call"). A few things to note about response calls:
332362
- They are done within a database transaction and changes are rolled back afterwards.

resources/views/partials/route.blade.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@
6969
```
7070

7171
@if(in_array('GET',$route['methods']) || (isset($route['showresponse']) && $route['showresponse']))
72+
@if(is_array($route['response']))
73+
@foreach($route['response'] as $response)
74+
> Example response ({{$response['status']}}):
75+
76+
```json
77+
@if(is_object($response['content']) || is_array($response['content']))
78+
{!! json_encode($response['content'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) !!}
79+
@else
80+
{!! json_encode(json_decode($response['content']), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) !!}
81+
@endif
82+
```
83+
@endforeach
84+
@else
7285
> Example response:
7386

7487
```json
@@ -79,6 +92,7 @@
7992
@endif
8093
```
8194
@endif
95+
@endif
8296

8397
### HTTP Request
8498
@foreach($route['methods'] as $method)

src/Tools/ResponseResolver.php

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
namespace Mpociot\ApiDoc\Tools;
44

55
use Illuminate\Routing\Route;
6+
use Symfony\Component\HttpFoundation\Response;
67
use Mpociot\ApiDoc\Tools\ResponseStrategies\ResponseTagStrategy;
78
use Mpociot\ApiDoc\Tools\ResponseStrategies\ResponseCallStrategy;
89
use Mpociot\ApiDoc\Tools\ResponseStrategies\ResponseFileStrategy;
910
use Mpociot\ApiDoc\Tools\ResponseStrategies\TransformerTagsStrategy;
1011

1112
class ResponseResolver
1213
{
14+
/**
15+
* @var array
16+
*/
1317
public static $strategies = [
1418
ResponseTagStrategy::class,
1519
TransformerTagsStrategy::class,
@@ -22,23 +26,43 @@ class ResponseResolver
2226
*/
2327
private $route;
2428

29+
/**
30+
* @param Route $route
31+
*/
2532
public function __construct(Route $route)
2633
{
2734
$this->route = $route;
2835
}
2936

37+
/**
38+
* @param array $tags
39+
* @param array $routeProps
40+
*
41+
* @return array|null
42+
*/
3043
private function resolve(array $tags, array $routeProps)
3144
{
32-
$response = null;
3345
foreach (static::$strategies as $strategy) {
3446
$strategy = new $strategy();
35-
$response = $strategy($this->route, $tags, $routeProps);
36-
if (! is_null($response)) {
37-
return $this->getResponseContent($response);
47+
48+
/** @var Response[]|null $response */
49+
$responses = $strategy($this->route, $tags, $routeProps);
50+
51+
if (! is_null($responses)) {
52+
return array_map(function (Response $response) {
53+
return ['status' => $response->getStatusCode(), 'content' => $this->getResponseContent($response)];
54+
}, $responses);
3855
}
3956
}
4057
}
4158

59+
/**
60+
* @param $route
61+
* @param $tags
62+
* @param $routeProps
63+
*
64+
* @return array
65+
*/
4266
public static function getResponse($route, $tags, $routeProps)
4367
{
4468
return (new static($route))->resolve($tags, $routeProps);

src/Tools/ResponseStrategies/ResponseCallStrategy.php

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
*/
1313
class ResponseCallStrategy
1414
{
15+
/**
16+
* @param Route $route
17+
* @param array $tags
18+
* @param array $routeProps
19+
*
20+
* @return array|null
21+
*/
1522
public function __invoke(Route $route, array $tags, array $routeProps)
1623
{
1724
$rulesToApply = $routeProps['rules']['response_calls'] ?? [];
@@ -21,8 +28,9 @@ public function __invoke(Route $route, array $tags, array $routeProps)
2128

2229
$this->configureEnvironment($rulesToApply);
2330
$request = $this->prepareRequest($route, $rulesToApply, $routeProps['body'], $routeProps['query']);
31+
2432
try {
25-
$response = $this->makeApiCall($request);
33+
$response = [$this->makeApiCall($request)];
2634
} catch (\Exception $e) {
2735
$response = null;
2836
} finally {
@@ -32,12 +40,25 @@ public function __invoke(Route $route, array $tags, array $routeProps)
3240
return $response;
3341
}
3442

43+
/**
44+
* @param array $rulesToApply
45+
*
46+
* @return void
47+
*/
3548
private function configureEnvironment(array $rulesToApply)
3649
{
3750
$this->startDbTransaction();
3851
$this->setEnvironmentVariables($rulesToApply['env'] ?? []);
3952
}
4053

54+
/**
55+
* @param Route $route
56+
* @param array $rulesToApply
57+
* @param array $bodyParams
58+
* @param array $queryParams
59+
*
60+
* @return Request
61+
*/
4162
private function prepareRequest(Route $route, array $rulesToApply, array $bodyParams, array $queryParams)
4263
{
4364
$uri = $this->replaceUrlParameterBindings($route, $rulesToApply['bindings'] ?? []);
@@ -77,6 +98,11 @@ protected function replaceUrlParameterBindings(Route $route, $bindings)
7798
return $uri;
7899
}
79100

101+
/**
102+
* @param array $env
103+
*
104+
* @return void
105+
*/
80106
private function setEnvironmentVariables(array $env)
81107
{
82108
foreach ($env as $name => $value) {
@@ -87,6 +113,9 @@ private function setEnvironmentVariables(array $env)
87113
}
88114
}
89115

116+
/**
117+
* @return void
118+
*/
90119
private function startDbTransaction()
91120
{
92121
try {
@@ -95,6 +124,9 @@ private function startDbTransaction()
95124
}
96125
}
97126

127+
/**
128+
* @return void
129+
*/
98130
private function endDbTransaction()
99131
{
100132
try {
@@ -103,11 +135,19 @@ private function endDbTransaction()
103135
}
104136
}
105137

138+
/**
139+
* @return void
140+
*/
106141
private function finish()
107142
{
108143
$this->endDbTransaction();
109144
}
110145

146+
/**
147+
* @param Request $request
148+
*
149+
* @return \Illuminate\Http\JsonResponse|mixed
150+
*/
111151
public function callDingoRoute(Request $request)
112152
{
113153
/** @var Dispatcher $dispatcher */
@@ -138,11 +178,23 @@ public function callDingoRoute(Request $request)
138178
return $response;
139179
}
140180

181+
/**
182+
* @param Route $route
183+
*
184+
* @return array
185+
*/
141186
public function getMethods(Route $route)
142187
{
143188
return array_diff($route->methods(), ['HEAD']);
144189
}
145190

191+
/**
192+
* @param Request $request
193+
* @param Route $route
194+
* @param array|null $headers
195+
*
196+
* @return Request
197+
*/
146198
private function addHeaders(Request $request, Route $route, $headers)
147199
{
148200
// set the proper domain
@@ -162,6 +214,12 @@ private function addHeaders(Request $request, Route $route, $headers)
162214
return $request;
163215
}
164216

217+
/**
218+
* @param Request $request
219+
* @param array $query
220+
*
221+
* @return Request
222+
*/
165223
private function addQueryParameters(Request $request, array $query)
166224
{
167225
$request->query->add($query);
@@ -170,13 +228,26 @@ private function addQueryParameters(Request $request, array $query)
170228
return $request;
171229
}
172230

231+
/**
232+
* @param Request $request
233+
* @param array $body
234+
*
235+
* @return Request
236+
*/
173237
private function addBodyParameters(Request $request, array $body)
174238
{
175239
$request->request->add($body);
176240

177241
return $request;
178242
}
179243

244+
/**
245+
* @param Request $request
246+
*
247+
* @throws \Exception
248+
*
249+
* @return \Illuminate\Http\JsonResponse|mixed|\Symfony\Component\HttpFoundation\Response
250+
*/
180251
private function makeApiCall(Request $request)
181252
{
182253
if (config('apidoc.router') == 'dingo') {
@@ -189,7 +260,9 @@ private function makeApiCall(Request $request)
189260
}
190261

191262
/**
192-
* @param $request
263+
* @param Request $request
264+
*
265+
* @throws \Exception
193266
*
194267
* @return \Symfony\Component\HttpFoundation\Response
195268
*/
@@ -202,6 +275,12 @@ private function callLaravelRoute(Request $request): \Symfony\Component\HttpFoun
202275
return $response;
203276
}
204277

278+
/**
279+
* @param Route $route
280+
* @param array $rulesToApply
281+
*
282+
* @return bool
283+
*/
205284
private function shouldMakeApiCall(Route $route, array $rulesToApply): bool
206285
{
207286
$allowedMethods = $rulesToApply['methods'] ?? [];

src/Tools/ResponseStrategies/ResponseFileStrategy.php

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,50 @@
33
namespace Mpociot\ApiDoc\Tools\ResponseStrategies;
44

55
use Illuminate\Routing\Route;
6+
use Illuminate\Http\JsonResponse;
67
use Mpociot\Reflection\DocBlock\Tag;
78

89
/**
910
* Get a response from from a file in the docblock ( @responseFile ).
1011
*/
1112
class ResponseFileStrategy
1213
{
14+
/**
15+
* @param Route $route
16+
* @param array $tags
17+
* @param array $routeProps
18+
*
19+
* @return array|null
20+
*/
1321
public function __invoke(Route $route, array $tags, array $routeProps)
1422
{
15-
return $this->getFileResponse($tags);
23+
return $this->getFileResponses($tags);
1624
}
1725

1826
/**
1927
* Get the response from the file if available.
2028
*
2129
* @param array $tags
2230
*
23-
* @return mixed
31+
* @return array|null
2432
*/
25-
protected function getFileResponse(array $tags)
33+
protected function getFileResponses(array $tags)
2634
{
2735
$responseFileTags = array_filter($tags, function ($tag) {
28-
return $tag instanceof Tag && strtolower($tag->getName()) == 'responsefile';
36+
return $tag instanceof Tag && strtolower($tag->getName()) === 'responsefile';
2937
});
38+
3039
if (empty($responseFileTags)) {
3140
return;
3241
}
33-
$responseFileTag = array_first($responseFileTags);
3442

35-
$json = json_decode(file_get_contents(storage_path($responseFileTag->getContent()), true), true);
43+
return array_map(function (Tag $responseFileTag) {
44+
preg_match('/^(\d{3})?\s?([\s\S]*)$/', $responseFileTag->getContent(), $result);
45+
46+
$status = $result[1] ?: 200;
47+
$content = $result[2] ? file_get_contents(storage_path($result[2]), true) : '{}';
3648

37-
return response()->json($json);
49+
return new JsonResponse(json_decode($content, true), (int) $status);
50+
}, $responseFileTags);
3851
}
3952
}

0 commit comments

Comments
 (0)