|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Tomaj\NetteApi\Handlers; |
|
4
|
|
|
|
|
5
|
|
|
use Nette\Http\Request; |
|
6
|
|
|
use Symfony\Component\Yaml\Yaml; |
|
7
|
|
|
use Tomaj\NetteApi\ApiDecider; |
|
8
|
|
|
use Tomaj\NetteApi\Authorization\BearerTokenAuthorization; |
|
9
|
|
|
use Tomaj\NetteApi\Link\ApiLink; |
|
10
|
|
|
use Tomaj\NetteApi\Output\JsonOutput; |
|
11
|
|
|
use Tomaj\NetteApi\Params\InputParam; |
|
12
|
|
|
use Tomaj\NetteApi\Params\JsonInputParam; |
|
13
|
|
|
use Tomaj\NetteApi\Response\JsonApiResponse; |
|
14
|
|
|
use Tomaj\NetteApi\Response\TextApiResponse; |
|
15
|
|
|
|
|
16
|
|
|
class SwaggerHandler extends BaseHandler |
|
17
|
|
|
{ |
|
18
|
|
|
/** @var ApiDecider */ |
|
19
|
|
|
private $apiDecider; |
|
20
|
|
|
|
|
21
|
|
|
/** @var ApiLink */ |
|
22
|
|
|
private $apiLink; |
|
23
|
|
|
|
|
24
|
|
|
/** @var Request */ |
|
25
|
|
|
private $request; |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* ApiListingHandler constructor. |
|
29
|
|
|
* |
|
30
|
|
|
* @param ApiDecider $apiDecider |
|
31
|
|
|
* @param ApiLink $apiLink |
|
32
|
|
|
*/ |
|
33
|
|
|
public function __construct(ApiDecider $apiDecider, ApiLink $apiLink, Request $request) |
|
34
|
|
|
{ |
|
35
|
|
|
parent::__construct(); |
|
36
|
|
|
$this->apiDecider = $apiDecider; |
|
37
|
|
|
$this->apiLink = $apiLink; |
|
38
|
|
|
$this->request = $request; |
|
39
|
|
|
} |
|
40
|
|
|
|
|
41
|
|
|
public function params() |
|
42
|
|
|
{ |
|
43
|
|
|
return [ |
|
44
|
|
|
new InputParam(InputParam::TYPE_GET, 'format', InputParam::OPTIONAL, ['json', 'yaml'], false, 'Response format') |
|
45
|
|
|
]; |
|
46
|
|
|
} |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* {@inheritdoc} |
|
50
|
|
|
*/ |
|
51
|
|
|
public function description() |
|
52
|
|
|
{ |
|
53
|
|
|
return 'Swagger API specification'; |
|
54
|
|
|
} |
|
55
|
|
|
|
|
56
|
|
|
/** |
|
57
|
|
|
* {@inheritdoc} |
|
58
|
|
|
*/ |
|
59
|
|
|
public function tags() |
|
60
|
|
|
{ |
|
61
|
|
|
return ['swagger', 'specification']; |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* {@inheritdoc} |
|
66
|
|
|
*/ |
|
67
|
|
|
public function handle($params) |
|
68
|
|
|
{ |
|
69
|
|
|
$version = $this->getEndpoint()->getVersion(); |
|
70
|
|
|
$handlers = $this->getHandlers($version); |
|
71
|
|
|
$scheme = $this->request->getUrl()->getScheme(); |
|
72
|
|
|
$host = $this->request->getUrl()->getHost(); |
|
73
|
|
|
$baseUrl = $scheme . '://' . $host; |
|
74
|
|
|
$basePath = $this->getBasePath($handlers, $baseUrl); |
|
75
|
|
|
|
|
76
|
|
|
$responses = [ |
|
77
|
|
|
404 => [ |
|
78
|
|
|
'description' => 'Not found', |
|
79
|
|
|
'schema' => [ |
|
80
|
|
|
'type' => 'object', |
|
81
|
|
|
'properties' => [ |
|
82
|
|
|
'status' => [ |
|
83
|
|
|
'type' => 'string', |
|
84
|
|
|
'enum' => ['error'], |
|
85
|
|
|
], |
|
86
|
|
|
'message' => [ |
|
87
|
|
|
'type' => 'string', |
|
88
|
|
|
'enum' => ['Unknown api endpoint'], |
|
89
|
|
|
], |
|
90
|
|
|
], |
|
91
|
|
|
'required' => ['status', 'message'], |
|
92
|
|
|
], |
|
93
|
|
|
], |
|
94
|
|
|
500 => [ |
|
95
|
|
|
'description' => 'Internal server error', |
|
96
|
|
|
'schema' => [ |
|
97
|
|
|
'type' => 'object', |
|
98
|
|
|
'properties' => [ |
|
99
|
|
|
'status' => [ |
|
100
|
|
|
'type' => 'string', |
|
101
|
|
|
'enum' => ['error'], |
|
102
|
|
|
], |
|
103
|
|
|
'message' => [ |
|
104
|
|
|
'type' => 'string', |
|
105
|
|
|
'enum' => ['Internal server error'], |
|
106
|
|
|
], |
|
107
|
|
|
], |
|
108
|
|
|
'required' => ['status', 'message'], |
|
109
|
|
|
], |
|
110
|
|
|
], |
|
111
|
|
|
]; |
|
112
|
|
|
|
|
113
|
|
|
$data = [ |
|
114
|
|
|
'swagger' => '2.0', |
|
115
|
|
|
'info' => [ |
|
116
|
|
|
'title' => $this->apiDecider->getTitle(), |
|
117
|
|
|
'description' => $this->apiDecider->getDescription(), |
|
118
|
|
|
'version' => $version, |
|
119
|
|
|
], |
|
120
|
|
|
'host' => $host, |
|
121
|
|
|
'schemes' => [ |
|
122
|
|
|
$scheme, |
|
123
|
|
|
], |
|
124
|
|
|
'securityDefinitions' => [ |
|
125
|
|
|
'Bearer' => [ |
|
126
|
|
|
'type' => 'apiKey', |
|
127
|
|
|
'name' => 'Authorization', |
|
128
|
|
|
'in' => 'header' |
|
129
|
|
|
], |
|
130
|
|
|
], |
|
131
|
|
|
'basePath' => $basePath, |
|
132
|
|
|
'produces' => [ |
|
133
|
|
|
'application/json' |
|
134
|
|
|
], |
|
135
|
|
|
'responses' => $responses, |
|
136
|
|
|
'paths' => $this->getHandlersList($handlers, $baseUrl, $basePath), |
|
137
|
|
|
]; |
|
138
|
|
|
|
|
139
|
|
View Code Duplication |
if ($params['format'] === 'yaml') { |
|
|
|
|
|
|
140
|
|
|
return new TextApiResponse(200, Yaml::dump($data, PHP_INT_MAX, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE), 'text/plain'); |
|
141
|
|
|
} |
|
142
|
|
|
return new JsonApiResponse(200, $data); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
/** |
|
146
|
|
|
* @param int $version |
|
147
|
|
|
* @return [] |
|
|
|
|
|
|
148
|
|
|
*/ |
|
149
|
|
View Code Duplication |
private function getHandlers($version) |
|
|
|
|
|
|
150
|
|
|
{ |
|
151
|
|
|
$versionHandlers = array_filter($this->apiDecider->getHandlers(), function ($handler) use ($version) { |
|
152
|
|
|
return $version == $handler['endpoint']->getVersion(); |
|
153
|
|
|
}); |
|
154
|
|
|
return $versionHandlers; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* Create handler list for specified version |
|
159
|
|
|
* |
|
160
|
|
|
* @param array $versionHandlers |
|
161
|
|
|
* @param string $basePath |
|
162
|
|
|
* |
|
163
|
|
|
* @return array |
|
164
|
|
|
*/ |
|
165
|
|
|
private function getHandlersList($versionHandlers, $baseUrl, $basePath) |
|
166
|
|
|
{ |
|
167
|
|
|
$list = []; |
|
168
|
|
|
foreach ($versionHandlers as $handler) { |
|
169
|
|
|
$path = str_replace([$baseUrl, $basePath], '', $this->apiLink->link($handler['endpoint'])); |
|
170
|
|
|
|
|
171
|
|
|
$responses = []; |
|
172
|
|
|
foreach ($handler['handler']->outputs() as $output) { |
|
173
|
|
|
if ($output instanceof JsonOutput) { |
|
174
|
|
|
$responses[$output->getCode()] = [ |
|
175
|
|
|
'description' => $output->getDescription(), |
|
176
|
|
|
'schema' => json_decode($output->getSchema(), true), |
|
177
|
|
|
]; |
|
178
|
|
|
} |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
$responses[400] = [ |
|
182
|
|
|
'description' => 'Bad request', |
|
183
|
|
|
'schema' => [ |
|
184
|
|
|
'type' => 'object', |
|
185
|
|
|
'properties' => [ |
|
186
|
|
|
'status' => [ |
|
187
|
|
|
'type' => 'string', |
|
188
|
|
|
'enum' => ['error'], |
|
189
|
|
|
], |
|
190
|
|
|
'message' => [ |
|
191
|
|
|
'type' => 'string', |
|
192
|
|
|
'enum' => ['Wrong input'], |
|
193
|
|
|
], |
|
194
|
|
|
], |
|
195
|
|
|
'required' => ['status', 'message'], |
|
196
|
|
|
], |
|
197
|
|
|
]; |
|
198
|
|
|
|
|
199
|
|
|
$responses[403] = [ |
|
200
|
|
|
'description' => 'Operation forbidden', |
|
201
|
|
|
'schema' => [ |
|
202
|
|
|
'type' => 'object', |
|
203
|
|
|
'properties' => [ |
|
204
|
|
|
'status' => [ |
|
205
|
|
|
'type' => 'string', |
|
206
|
|
|
'enum' => ['error'], |
|
207
|
|
|
], |
|
208
|
|
|
'message' => [ |
|
209
|
|
|
'type' => 'string', |
|
210
|
|
|
'enum' => ['Authorization header HTTP_Authorization is not set', 'Authorization header contains invalid structure'], |
|
211
|
|
|
], |
|
212
|
|
|
], |
|
213
|
|
|
'required' => ['status', 'message'], |
|
214
|
|
|
], |
|
215
|
|
|
]; |
|
216
|
|
|
|
|
217
|
|
|
$responses[500] = [ |
|
218
|
|
|
'description' => 'Internal server error', |
|
219
|
|
|
'schema' => [ |
|
220
|
|
|
'type' => 'object', |
|
221
|
|
|
'properties' => [ |
|
222
|
|
|
'status' => [ |
|
223
|
|
|
'type' => 'string', |
|
224
|
|
|
'enum' => ['error'], |
|
225
|
|
|
], |
|
226
|
|
|
'message' => [ |
|
227
|
|
|
'type' => 'string', |
|
228
|
|
|
'enum' => ['Internal server error'], |
|
229
|
|
|
], |
|
230
|
|
|
], |
|
231
|
|
|
'required' => ['status', 'message'], |
|
232
|
|
|
], |
|
233
|
|
|
]; |
|
234
|
|
|
|
|
235
|
|
|
$settings = [ |
|
236
|
|
|
'summary' => $handler['handler']->description(), |
|
237
|
|
|
'tags' => $handler['handler']->tags(), |
|
238
|
|
|
'parameters' => $this->createParamsList($handler['handler']), |
|
239
|
|
|
|
|
240
|
|
|
]; |
|
241
|
|
View Code Duplication |
if ($handler['authorization'] instanceof BearerTokenAuthorization) { |
|
|
|
|
|
|
242
|
|
|
$settings['security'] = [ |
|
243
|
|
|
[ |
|
244
|
|
|
'Bearer' => [], |
|
245
|
|
|
], |
|
246
|
|
|
]; |
|
247
|
|
|
} |
|
248
|
|
|
$settings['responses'] = $responses; |
|
249
|
|
|
$list[$path][strtolower($handler['endpoint']->getMethod())] = $settings; |
|
250
|
|
|
} |
|
251
|
|
|
return $list; |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
View Code Duplication |
private function getBasePath($handlers, $baseUrl) |
|
|
|
|
|
|
255
|
|
|
{ |
|
256
|
|
|
$basePath = null; |
|
257
|
|
|
foreach ($handlers as $handler) { |
|
258
|
|
|
$basePath = $this->getLongestCommonSubstring($basePath, $this->apiLink->link($handler['endpoint'])); |
|
259
|
|
|
} |
|
260
|
|
|
return rtrim(str_replace($baseUrl, '', $basePath), '/'); |
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
|
View Code Duplication |
private function getLongestCommonSubstring($path1, $path2) |
|
|
|
|
|
|
264
|
|
|
{ |
|
265
|
|
|
if ($path1 === null) { |
|
266
|
|
|
return $path2; |
|
267
|
|
|
} |
|
268
|
|
|
$commonSubstring = ''; |
|
269
|
|
|
$shortest = min(strlen($path1), strlen($path2)); |
|
270
|
|
|
for ($i = 0; $i <= $shortest; ++$i) { |
|
271
|
|
|
if (substr($path1, 0, $i) !== substr($path2, 0, $i)) { |
|
272
|
|
|
break; |
|
273
|
|
|
} |
|
274
|
|
|
$commonSubstring = substr($path1, 0, $i); |
|
275
|
|
|
} |
|
276
|
|
|
return $commonSubstring; |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
/** |
|
280
|
|
|
* Create array with params for specified handler |
|
281
|
|
|
* |
|
282
|
|
|
* @param ApiHandlerInterface $handler |
|
283
|
|
|
* |
|
284
|
|
|
* @return array |
|
285
|
|
|
*/ |
|
286
|
|
|
private function createParamsList(ApiHandlerInterface $handler) |
|
287
|
|
|
{ |
|
288
|
|
|
return array_map(function (InputParam $param) { |
|
289
|
|
|
$parameter = [ |
|
290
|
|
|
'name' => $param->getKey(), |
|
291
|
|
|
'in' => $this->createIn($param->getType()), |
|
292
|
|
|
'required' => $param->isRequired(), |
|
293
|
|
|
'description' => $param->getDescription(), |
|
294
|
|
|
]; |
|
295
|
|
|
|
|
296
|
|
|
if ($param instanceof JsonInputParam) { |
|
297
|
|
|
$parameter['schema'] = json_decode($param->getSchema(), true); |
|
298
|
|
|
} else { |
|
299
|
|
|
$parameter['type'] = $param->isMulti() ? 'list' : 'string'; |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
if ($param->getAvailableValues()) { |
|
303
|
|
|
$parameter['enum'] = $param->getAvailableValues(); |
|
304
|
|
|
} |
|
305
|
|
|
return $parameter; |
|
306
|
|
|
}, $handler->params()); |
|
307
|
|
|
} |
|
308
|
|
|
|
|
309
|
|
View Code Duplication |
private function createIn($type) |
|
|
|
|
|
|
310
|
|
|
{ |
|
311
|
|
|
if ($type == InputParam::TYPE_GET) { |
|
312
|
|
|
return 'query'; |
|
313
|
|
|
} |
|
314
|
|
|
if ($type == InputParam::TYPE_COOKIE) { |
|
315
|
|
|
return 'cookie'; |
|
316
|
|
|
} |
|
317
|
|
|
return 'body'; |
|
318
|
|
|
} |
|
319
|
|
|
} |
|
320
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.