Completed
Pull Request — master (#56)
by Michal
12:52
created

SwaggerHandler::getBasePath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 8
Ratio 100 %

Importance

Changes 0
Metric Value
dl 8
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
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') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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 []
0 ignored issues
show
Documentation introduced by
The doc-type [] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
148
     */
149 View Code Duplication
    private function getHandlers($version)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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