• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RonasIT / laravel-swagger / 6609913210

23 Oct 2023 07:12AM UTC coverage: 97.829% (-0.1%) from 97.967%
6609913210

push

github

web-flow
Merge pull request #108 from RonasIT/45-throw-exception-for-empty-or-not-exists-files

Throw exception for empty or not exists files

4 of 4 new or added lines in 3 files covered. (100.0%)

721 of 737 relevant lines covered (97.83%)

15.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.49
/src/Services/SwaggerService.php
1
<?php
2

3
namespace RonasIT\Support\AutoDoc\Services;
4

5
use Illuminate\Container\Container;
6
use Illuminate\Http\Request;
7
use Illuminate\Http\Testing\File;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Str;
10
use ReflectionClass;
11
use RonasIT\Support\AutoDoc\Exceptions\DocFileNotExistsException;
12
use RonasIT\Support\AutoDoc\Exceptions\EmptyContactEmailException;
13
use RonasIT\Support\AutoDoc\Exceptions\EmptyDocFileException;
14
use RonasIT\Support\AutoDoc\Exceptions\InvalidDriverClassException;
15
use RonasIT\Support\AutoDoc\Exceptions\LegacyConfigException;
16
use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\InvalidSwaggerSpecException;
17
use RonasIT\Support\AutoDoc\Exceptions\SwaggerDriverClassNotFoundException;
18
use RonasIT\Support\AutoDoc\Exceptions\WrongSecurityConfigException;
19
use RonasIT\Support\AutoDoc\Interfaces\SwaggerDriverInterface;
20
use RonasIT\Support\AutoDoc\Traits\GetDependenciesTrait;
21
use RonasIT\Support\AutoDoc\Validators\SwaggerSpecValidator;
22
use Symfony\Component\HttpFoundation\Response;
23

24
/**
25
 * @property SwaggerDriverInterface $driver
26
 */
27
class SwaggerService
28
{
29
    use GetDependenciesTrait;
30

31
    public const SWAGGER_VERSION = '2.0';
32

33
    protected $driver;
34

35
    protected $data;
36
    protected $config;
37
    protected $container;
38
    private $uri;
39
    private $method;
40
    /**
41
     * @var Request
42
     */
43
    private $request;
44
    private $item;
45
    private $security;
46

47
    protected $ruleToTypeMap = [
48
        'array' => 'object',
49
        'boolean' => 'boolean',
50
        'date' => 'date',
51
        'digits' => 'integer',
52
        'integer' => 'integer',
53
        'numeric' => 'double',
54
        'string' => 'string',
55
        'int' => 'integer'
56
    ];
57

58
    public function __construct(Container $container)
59
    {
60
        $this->initConfig();
75✔
61

62
        $this->setDriver();
73✔
63

64
        if (config('app.env') == 'testing') {
71✔
65
            $this->container = $container;
71✔
66

67
            $this->security = $this->config['security'];
71✔
68

69
            $this->data = $this->driver->getTmpData();
71✔
70

71
            if (!empty($this->data)) {
71✔
72
                $this->validateSpec($this->data);
52✔
73
            } else {
74
                $this->data = $this->generateEmptyData();
19✔
75

76
                $this->driver->saveTmpData($this->data);
17✔
77
            }
78
        }
79
    }
80

81
    protected function initConfig()
82
    {
83
        $this->config = config('auto-doc');
75✔
84

85
        $version = Arr::get($this->config, 'config_version');
75✔
86

87
        if (empty($version)) {
75✔
88
            throw new LegacyConfigException();
1✔
89
        }
90

91
        $packageConfigs = require __DIR__ . '/../../config/auto-doc.php';
74✔
92

93
        if (version_compare($packageConfigs['config_version'], $version, '>')) {
74✔
94
            throw new LegacyConfigException();
1✔
95
        }
96
    }
97

98
    protected function setDriver()
99
    {
100
        $driver = $this->config['driver'];
73✔
101
        $className = Arr::get($this->config, "drivers.{$driver}.class");
73✔
102

103
        if (!class_exists($className)) {
73✔
104
            throw new SwaggerDriverClassNotFoundException($className);
1✔
105
        } else {
106
            $this->driver = app($className);
72✔
107
        }
108

109
        if (!$this->driver instanceof SwaggerDriverInterface) {
72✔
110
            throw new InvalidDriverClassException($driver);
1✔
111
        }
112
    }
113

114
    protected function generateEmptyData(): array
115
    {
116
        // client must enter at least `contact.email` to generate a default `info` block
117
        // otherwise an exception will be called
118
        if (!empty($this->config['info']) && !Arr::get($this->config, 'info.contact.email')) {
19✔
119
            throw new EmptyContactEmailException();
1✔
120
        }
121

122
        $data = [
18✔
123
            'swagger' => self::SWAGGER_VERSION,
18✔
124
            'host' => $this->getAppUrl(),
18✔
125
            'basePath' => $this->config['basePath'],
18✔
126
            'schemes' => $this->config['schemes'],
18✔
127
            'paths' => [],
18✔
128
            'definitions' => $this->config['definitions'],
18✔
129
            'info' => $this->prepareInfo($this->config['info'])
18✔
130
        ];
18✔
131

132
        $securityDefinitions = $this->generateSecurityDefinition();
18✔
133

134
        if (!empty($securityDefinitions)) {
17✔
135
            $data['securityDefinitions'] = $securityDefinitions;
2✔
136
        }
137

138
        return $data;
17✔
139
    }
140

141
    protected function getAppUrl(): string
142
    {
143
        $url = config('app.url');
18✔
144

145
        return str_replace(['http://', 'https://', '/'], '', $url);
18✔
146
    }
147

148
    protected function generateSecurityDefinition(): ?array
149
    {
150
        if (empty($this->security)) {
18✔
151
            return null;
15✔
152
        }
153

154
        return [
3✔
155
            $this->security => $this->generateSecurityDefinitionObject($this->security)
3✔
156
        ];
3✔
157
    }
158

159
    protected function generateSecurityDefinitionObject($type): array
160
    {
161
        switch ($type) {
162
            case 'jwt':
3✔
163
                return [
1✔
164
                    'type' => 'apiKey',
1✔
165
                    'name' => 'authorization',
1✔
166
                    'in' => 'header'
1✔
167
                ];
1✔
168

169
            case 'laravel':
2✔
170
                return [
1✔
171
                    'type' => 'apiKey',
1✔
172
                    'name' => 'Cookie',
1✔
173
                    'in' => 'header'
1✔
174
                ];
1✔
175
            default:
176
                throw new WrongSecurityConfigException();
1✔
177
        }
178
    }
179

180
    public function addData(Request $request, $response)
181
    {
182
        $this->request = $request;
18✔
183

184
        $this->prepareItem();
18✔
185

186
        $this->parseRequest();
18✔
187
        $this->parseResponse($response);
18✔
188

189
        $this->driver->saveTmpData($this->data);
18✔
190
    }
191

192
    protected function prepareItem()
193
    {
194
        $this->uri = "/{$this->getUri()}";
18✔
195
        $this->method = strtolower($this->request->getMethod());
18✔
196

197
        if (empty(Arr::get($this->data, "paths.{$this->uri}.{$this->method}"))) {
18✔
198
            $this->data['paths'][$this->uri][$this->method] = [
18✔
199
                'tags' => [],
18✔
200
                'consumes' => [],
18✔
201
                'produces' => [],
18✔
202
                'parameters' => $this->getPathParams(),
18✔
203
                'responses' => [],
18✔
204
                'security' => [],
18✔
205
                'description' => ''
18✔
206
            ];
18✔
207
        }
208

209
        $this->item = &$this->data['paths'][$this->uri][$this->method];
18✔
210
    }
211

212
    protected function getUri()
213
    {
214
        $uri = $this->request->route()->uri();
18✔
215
        $basePath = preg_replace("/^\//", '', $this->config['basePath']);
18✔
216
        $preparedUri = preg_replace("/^{$basePath}/", '', $uri);
18✔
217

218
        return preg_replace("/^\//", '', $preparedUri);
18✔
219
    }
220

221
    protected function getPathParams(): array
222
    {
223
        $params = [];
18✔
224

225
        preg_match_all('/{.*?}/', $this->uri, $params);
18✔
226

227
        $params = Arr::collapse($params);
18✔
228

229
        $result = [];
18✔
230

231
        foreach ($params as $param) {
18✔
232
            $key = preg_replace('/[{}]/', '', $param);
2✔
233

234
            $result[] = [
2✔
235
                'in' => 'path',
2✔
236
                'name' => $key,
2✔
237
                'description' => '',
2✔
238
                'required' => true,
2✔
239
                'type' => 'string'
2✔
240
            ];
2✔
241
        }
242

243
        return $result;
18✔
244
    }
245

246
    protected function parseRequest()
247
    {
248
        $this->saveConsume();
18✔
249
        $this->saveTags();
18✔
250
        $this->saveSecurity();
18✔
251

252
        $concreteRequest = $this->getConcreteRequest();
18✔
253

254
        if (empty($concreteRequest)) {
18✔
255
            $this->item['description'] = '';
2✔
256

257
            return;
2✔
258
        }
259

260
        $annotations = $this->getClassAnnotations($concreteRequest);
16✔
261

262
        $this->saveParameters($concreteRequest, $annotations);
16✔
263
        $this->saveDescription($concreteRequest, $annotations);
16✔
264
    }
265

266
    protected function parseResponse($response)
267
    {
268
        $produceList = $this->data['paths'][$this->uri][$this->method]['produces'];
18✔
269

270
        $produce = $response->headers->get('Content-type');
18✔
271

272
        if (is_null($produce)) {
18✔
273
            $produce = 'text/plain';
1✔
274
        }
275

276
        if (!in_array($produce, $produceList)) {
18✔
277
            $this->item['produces'][] = $produce;
18✔
278
        }
279

280
        $responses = $this->item['responses'];
18✔
281

282
        $responseExampleLimitCount = config('auto-doc.response_example_limit_count');
18✔
283

284
        $content = json_decode($response->getContent(), true);
18✔
285

286
        if (!empty($responseExampleLimitCount)) {
18✔
287
            if (!empty($content['data'])) {
18✔
288
                $limitedResponseData = array_slice($content['data'], 0, $responseExampleLimitCount, true);
2✔
289
                $content['data'] = $limitedResponseData;
2✔
290
                $content['to'] = count($limitedResponseData);
2✔
291
                $content['total'] = count($limitedResponseData);
2✔
292
            }
293
        }
294

295
        if (!empty($content['exception'])) {
18✔
296
            $uselessKeys = array_keys(Arr::except($content, ['message']));
1✔
297

298
            $content = Arr::except($content, $uselessKeys);
1✔
299
        }
300

301
        $code = $response->getStatusCode();
18✔
302

303
        if (!in_array($code, $responses)) {
18✔
304
            $this->saveExample(
18✔
305
                $code,
18✔
306
                json_encode($content, JSON_PRETTY_PRINT),
18✔
307
                $produce
18✔
308
            );
18✔
309
        }
310
    }
311

312
    protected function saveExample($code, $content, $produce)
313
    {
314
        $description = $this->getResponseDescription($code);
18✔
315
        $availableContentTypes = [
18✔
316
            'application',
18✔
317
            'text',
18✔
318
            'image',
18✔
319
        ];
18✔
320
        $explodedContentType = explode('/', $produce);
18✔
321

322
        if (in_array($explodedContentType[0], $availableContentTypes)) {
18✔
323
            $this->item['responses'][$code] = $this->makeResponseExample($content, $produce, $description);
17✔
324
        } else {
325
            $this->item['responses'][$code] = '*Unavailable for preview*';
1✔
326
        }
327
    }
328

329
    protected function makeResponseExample($content, $mimeType, $description = ''): array
330
    {
331
        $responseExample = ['description' => $description];
17✔
332

333
        if ($mimeType === 'application/json') {
17✔
334
            $responseExample['schema'] = ['example' => json_decode($content, true)];
14✔
335
        } elseif ($mimeType === 'application/pdf') {
3✔
336
            $responseExample['schema'] = ['example' => base64_encode($content)];
1✔
337
        } else {
338
            $responseExample['examples']['example'] = $content;
2✔
339
        }
340

341
        return $responseExample;
17✔
342
    }
343

344
    protected function saveParameters($request, array $annotations)
345
    {
346
        $formRequest = new $request();
16✔
347
        $formRequest->setUserResolver($this->request->getUserResolver());
16✔
348
        $formRequest->setRouteResolver($this->request->getRouteResolver());
16✔
349
        $rules = method_exists($formRequest, 'rules') ? $this->prepareRules($formRequest->rules()) : [];
16✔
350
        $attributes = method_exists($formRequest, 'attributes') ? $formRequest->attributes() : [];
16✔
351

352
        $actionName = $this->getActionName($this->uri);
16✔
353

354
        if (in_array($this->method, ['get', 'delete'])) {
16✔
355
            $this->saveGetRequestParameters($rules, $attributes, $annotations);
12✔
356
        } else {
357
            $this->savePostRequestParameters($actionName, $rules, $attributes, $annotations);
4✔
358
        }
359
    }
360

361
    protected function prepareRules(array $rules): array
362
    {
363
        $preparedRules = [];
16✔
364

365
        foreach ($rules as $field => $rulesField) {
16✔
366
            if (is_array($rulesField)) {
16✔
367
                $rulesField = array_map(function ($rule) {
14✔
368
                    return $this->getRuleAsString($rule);
14✔
369
                }, $rulesField);
14✔
370

371
                $preparedRules[$field] = implode('|', $rulesField);
14✔
372
            } else {
373
                $preparedRules[$field] = $this->getRuleAsString($rulesField);
16✔
374
            }
375
        }
376

377
        return $preparedRules;
16✔
378
    }
379

380
    protected function getRuleAsString($rule): string
381
    {
382
        if (is_object($rule)) {
16✔
383
            if (method_exists($rule, '__toString')) {
14✔
384
                return $rule->__toString();
14✔
385
            }
386

387
            $shortName = Str::afterLast(get_class($rule), '\\');
14✔
388

389
            $ruleName = preg_replace('/Rule$/', '', $shortName);
14✔
390

391
            return Str::snake($ruleName);
14✔
392
        }
393

394
        return $rule;
16✔
395
    }
396

397
    protected function saveGetRequestParameters($rules, array $attributes, array $annotations)
398
    {
399
        foreach ($rules as $parameter => $rule) {
12✔
400
            $validation = explode('|', $rule);
12✔
401

402
            $description = Arr::get($annotations, $parameter);
12✔
403

404
            if (empty($description)) {
12✔
405
                $description = Arr::get($attributes, $parameter, implode(', ', $validation));
12✔
406
            }
407

408
            $existedParameter = Arr::first($this->item['parameters'], function ($existedParameter) use ($parameter) {
12✔
409
                return $existedParameter['name'] == $parameter;
10✔
410
            });
12✔
411

412
            if (empty($existedParameter)) {
12✔
413
                $parameterDefinition = [
12✔
414
                    'in' => 'query',
12✔
415
                    'name' => $parameter,
12✔
416
                    'description' => $description,
12✔
417
                    'type' => $this->getParameterType($validation)
12✔
418
                ];
12✔
419
                if (in_array('required', $validation)) {
12✔
420
                    $parameterDefinition['required'] = true;
12✔
421
                }
422

423
                $this->item['parameters'][] = $parameterDefinition;
12✔
424
            }
425
        }
426
    }
427

428
    protected function savePostRequestParameters($actionName, $rules, array $attributes, array $annotations)
429
    {
430
        if ($this->requestHasMoreProperties($actionName)) {
4✔
431
            if ($this->requestHasBody()) {
4✔
432
                $this->item['parameters'][] = [
4✔
433
                    'in' => 'body',
4✔
434
                    'name' => 'body',
4✔
435
                    'description' => '',
4✔
436
                    'required' => true,
4✔
437
                    'schema' => [
4✔
438
                        "\$ref" => "#/definitions/{$actionName}Object"
4✔
439
                    ]
4✔
440
                ];
4✔
441
            }
442

443
            $this->saveDefinitions($actionName, $rules, $attributes, $annotations);
4✔
444
        }
445
    }
446

447
    protected function saveDefinitions($objectName, $rules, $attributes, array $annotations)
448
    {
449
        $data = [
4✔
450
            'type' => 'object',
4✔
451
            'properties' => []
4✔
452
        ];
4✔
453

454
        foreach ($rules as $parameter => $rule) {
4✔
455
            $rulesArray = (is_array($rule)) ? $rule : explode('|', $rule);
4✔
456
            $parameterType = $this->getParameterType($rulesArray);
4✔
457
            $this->saveParameterType($data, $parameter, $parameterType);
4✔
458

459
            $uselessRules = $this->ruleToTypeMap;
4✔
460
            $uselessRules['required'] = 'required';
4✔
461

462
            if (in_array('required', $rulesArray)) {
4✔
463
                $data['required'][] = $parameter;
4✔
464
            }
465

466
            $rulesArray = array_flip(array_diff_key(array_flip($rulesArray), $uselessRules));
4✔
467

468
            $this->saveParameterDescription($data, $parameter, $rulesArray, $attributes, $annotations);
4✔
469
        }
470

471
        $data['example'] = $this->generateExample($data['properties']);
4✔
472
        $this->data['definitions'][$objectName . 'Object'] = $data;
4✔
473
    }
474

475
    protected function getParameterType(array $validation): string
476
    {
477
        $validationRules = $this->ruleToTypeMap;
16✔
478
        $validationRules['email'] = 'string';
16✔
479

480
        $parameterType = 'string';
16✔
481

482
        foreach ($validation as $item) {
16✔
483
            if (in_array($item, array_keys($validationRules))) {
16✔
484
                return $validationRules[$item];
15✔
485
            }
486
        }
487

488
        return $parameterType;
15✔
489
    }
490

491
    protected function saveParameterType(&$data, $parameter, $parameterType)
492
    {
493
        $data['properties'][$parameter] = [
4✔
494
            'type' => $parameterType
4✔
495
        ];
4✔
496
    }
497

498
    protected function saveParameterDescription(&$data, $parameter, array $rulesArray, array $attributes, array $annotations)
499
    {
500
        $description = Arr::get($annotations, $parameter);
4✔
501

502
        if (empty($description)) {
4✔
503
            $description = Arr::get($attributes, $parameter, implode(', ', $rulesArray));
4✔
504
        }
505

506
        $data['properties'][$parameter]['description'] = $description;
4✔
507
    }
508

509
    protected function requestHasMoreProperties($actionName): bool
510
    {
511
        $requestParametersCount = count($this->request->all());
4✔
512

513
        if (isset($this->data['definitions'][$actionName . 'Object']['properties'])) {
4✔
514
            $objectParametersCount = count($this->data['definitions'][$actionName . 'Object']['properties']);
1✔
515
        } else {
516
            $objectParametersCount = 0;
3✔
517
        }
518

519
        return $requestParametersCount > $objectParametersCount;
4✔
520
    }
521

522
    protected function requestHasBody(): bool
523
    {
524
        $parameters = $this->data['paths'][$this->uri][$this->method]['parameters'];
4✔
525

526
        $bodyParamExisted = Arr::where($parameters, function ($value) {
4✔
527
            return $value['name'] == 'body';
1✔
528
        });
4✔
529

530
        return empty($bodyParamExisted);
4✔
531
    }
532

533
    public function getConcreteRequest()
534
    {
535
        $controller = $this->request->route()->getActionName();
18✔
536

537
        if ($controller == 'Closure') {
18✔
538
            return null;
1✔
539
        }
540

541
        $explodedController = explode('@', $controller);
17✔
542

543
        $class = $explodedController[0];
17✔
544
        $method = $explodedController[1];
17✔
545

546
        $instance = app($class);
17✔
547
        $route = $this->request->route();
17✔
548

549
        $parameters = $this->resolveClassMethodDependencies(
17✔
550
            $route->parametersWithoutNulls(),
17✔
551
            $instance,
17✔
552
            $method
17✔
553
        );
17✔
554

555
        return Arr::first($parameters, function ($key) {
17✔
556
            return preg_match('/Request/', $key);
17✔
557
        });
17✔
558
    }
559

560
    public function saveConsume()
561
    {
562
        $consumeList = $this->data['paths'][$this->uri][$this->method]['consumes'];
18✔
563
        $consume = $this->request->header('Content-Type');
18✔
564

565
        if (!empty($consume) && !in_array($consume, $consumeList)) {
18✔
566
            $this->item['consumes'][] = $consume;
13✔
567
        }
568
    }
569

570
    public function saveTags()
571
    {
572
        $tagIndex = 1;
18✔
573

574
        $explodedUri = explode('/', $this->uri);
18✔
575

576
        $tag = Arr::get($explodedUri, $tagIndex);
18✔
577

578
        $this->item['tags'] = [$tag];
18✔
579
    }
580

581
    public function saveDescription($request, array $annotations)
582
    {
583
        $this->item['summary'] = $this->getSummary($request, $annotations);
16✔
584

585
        $description = Arr::get($annotations, 'description');
16✔
586

587
        if (!empty($description)) {
16✔
588
            $this->item['description'] = $description;
1✔
589
        }
590
    }
591

592
    protected function saveSecurity()
593
    {
594
        if ($this->requestSupportAuth()) {
18✔
595
            $this->addSecurityToOperation();
3✔
596
        }
597
    }
598

599
    protected function addSecurityToOperation()
600
    {
601
        $security = &$this->data['paths'][$this->uri][$this->method]['security'];
3✔
602

603
        if (empty($security)) {
3✔
604
            $security[] = [
3✔
605
                "{$this->security}" => []
3✔
606
            ];
3✔
607
        }
608
    }
609

610
    protected function getSummary($request, array $annotations)
611
    {
612
        $summary = Arr::get($annotations, 'summary');
16✔
613

614
        if (empty($summary)) {
16✔
615
            $summary = $this->parseRequestName($request);
15✔
616
        }
617

618
        return $summary;
16✔
619
    }
620

621
    protected function requestSupportAuth(): bool
622
    {
623
        switch ($this->security) {
18✔
624
            case 'jwt':
18✔
625
                $header = $this->request->header('authorization');
5✔
626
                break;
5✔
627
            case 'laravel':
13✔
628
                $header = $this->request->cookie('__ym_uid');
2✔
629
                break;
2✔
630
        }
631

632
        return !empty($header);
18✔
633
    }
634

635
    protected function parseRequestName($request)
636
    {
637
        $explodedRequest = explode('\\', $request);
15✔
638
        $requestName = array_pop($explodedRequest);
15✔
639
        $summaryName = str_replace('Request', '', $requestName);
15✔
640

641
        $underscoreRequestName = $this->camelCaseToUnderScore($summaryName);
15✔
642

643
        return preg_replace('/[_]/', ' ', $underscoreRequestName);
15✔
644
    }
645

646
    protected function getResponseDescription($code)
647
    {
648
        $defaultDescription = Response::$statusTexts[$code];
18✔
649

650
        $request = $this->getConcreteRequest();
18✔
651

652
        if (empty($request)) {
18✔
653
            return $defaultDescription;
2✔
654
        }
655

656
        $annotations = $this->getClassAnnotations($request);
16✔
657

658
        $localDescription = Arr::get($annotations, "_{$code}");
16✔
659

660
        if (!empty($localDescription)) {
16✔
661
            return $localDescription;
1✔
662
        }
663

664
        return Arr::get($this->config, "defaults.code-descriptions.{$code}", $defaultDescription);
15✔
665
    }
666

667
    protected function getActionName($uri): string
668
    {
669
        $action = preg_replace('[\/]', '', $uri);
16✔
670

671
        return Str::camel($action);
16✔
672
    }
673

674
    /**
675
     * @deprecated method is not in use
676
     * @codeCoverageIgnore
677
     */
678
    protected function saveTempData()
679
    {
680
        $exportFile = Arr::get($this->config, 'files.temporary');
681
        $data = json_encode($this->data);
682

683
        file_put_contents($exportFile, $data);
684
    }
685

686
    public function saveProductionData()
687
    {
688
        $this->driver->saveData();
1✔
689
    }
690

691
    public function getDocFileContent()
692
    {
693
        $documentation = $this->driver->getDocumentation();
4✔
694

695
        $additionalDocs = config('auto-doc.additional_paths', []);
4✔
696

697
        foreach ($additionalDocs as $filePath) {
4✔
698
            $fullFilePath = base_path($filePath);
2✔
699

700
            if (!file_exists($fullFilePath)) {
2✔
701
                throw new DocFileNotExistsException($fullFilePath);
×
702
            }
703

704
            $fileContent = json_decode(file_get_contents($fullFilePath), true);
2✔
705

706
            if (empty($fileContent)) {
2✔
707
                throw new EmptyDocFileException($fullFilePath);
×
708
            }
709

710
            try {
711
                $this->validateSpec($fileContent);
2✔
712
            } catch (InvalidSwaggerSpecException $exception) {
1✔
713
                report($exception);
1✔
714

715
                continue;
1✔
716
            }
717

718
            $paths = array_keys($fileContent['paths']);
1✔
719

720
            foreach ($paths as $path) {
1✔
721
                $additionalDocPath = $fileContent['paths'][$path];
1✔
722

723
                if (empty($documentation['paths'][$path])) {
1✔
724
                    $documentation['paths'][$path] = $additionalDocPath;
1✔
725
                } else {
726
                    $methods = array_keys($documentation['paths'][$path]);
1✔
727
                    $additionalDocMethods = array_keys($additionalDocPath);
1✔
728

729
                    foreach ($additionalDocMethods as $method) {
1✔
730
                        if (!in_array($method, $methods)) {
1✔
731
                            $documentation['paths'][$path][$method] = $additionalDocPath[$method];
1✔
732
                        }
733
                    }
734
                }
735
            }
736

737
            $definitions = array_keys($fileContent['definitions']);
1✔
738

739
            foreach ($definitions as $definition) {
1✔
740
                if (empty($documentation['definitions'][$definition])) {
1✔
741
                    $documentation['definitions'][$definition] = $fileContent['definitions'][$definition];
1✔
742
                }
743
            }
744
        }
745

746
        return $documentation;
4✔
747
    }
748

749
    protected function camelCaseToUnderScore($input): string
750
    {
751
        preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
15✔
752
        $ret = $matches[0];
15✔
753

754
        foreach ($ret as &$match) {
15✔
755
            $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
15✔
756
        }
757

758
        return implode('_', $ret);
15✔
759
    }
760

761
    protected function generateExample($properties): array
762
    {
763
        $parameters = $this->replaceObjectValues($this->request->all());
4✔
764
        $example = [];
4✔
765

766
        $this->replaceNullValues($parameters, $properties, $example);
4✔
767

768
        return $example;
4✔
769
    }
770

771
    protected function replaceObjectValues($parameters): array
772
    {
773
        $classNamesValues = [
4✔
774
            File::class => '[uploaded_file]',
4✔
775
        ];
4✔
776

777
        $parameters = Arr::dot($parameters);
4✔
778
        $returnParameters = [];
4✔
779

780
        foreach ($parameters as $parameter => $value) {
4✔
781
            if (is_object($value)) {
4✔
782
                $class = get_class($value);
1✔
783

784
                $value = Arr::get($classNamesValues, $class, $class);
1✔
785
            }
786

787
            Arr::set($returnParameters, $parameter, $value);
4✔
788
        }
789

790
        return $returnParameters;
4✔
791
    }
792

793
    protected function getClassAnnotations($class): array
794
    {
795
        $reflection = new ReflectionClass($class);
16✔
796

797
        $annotations = $reflection->getDocComment();
16✔
798

799
        $blocks = explode("\n", $annotations);
16✔
800

801
        $result = [];
16✔
802

803
        foreach ($blocks as $block) {
16✔
804
            if (Str::contains($block, '@')) {
16✔
805
                $index = strpos($block, '@');
1✔
806
                $block = substr($block, $index);
1✔
807
                $exploded = explode(' ', $block);
1✔
808

809
                $paramName = str_replace('@', '', array_shift($exploded));
1✔
810
                $paramValue = implode(' ', $exploded);
1✔
811

812
                $result[$paramName] = $paramValue;
1✔
813
            }
814
        }
815

816
        return $result;
16✔
817
    }
818

819
    /**
820
     * NOTE: All functions below are temporary solution for
821
     * this issue: https://github.com/OAI/OpenAPI-Specification/issues/229
822
     * We hope swagger developers will resolve this problem in next release of Swagger OpenAPI
823
     * */
824
    protected function replaceNullValues($parameters, $types, &$example)
825
    {
826
        foreach ($parameters as $parameter => $value) {
4✔
827
            if (is_null($value) && array_key_exists($parameter, $types)) {
4✔
828
                $example[$parameter] = $this->getDefaultValueByType($types[$parameter]['type']);
3✔
829
            } elseif (is_array($value)) {
4✔
830
                $this->replaceNullValues($value, $types, $example[$parameter]);
1✔
831
            } else {
832
                $example[$parameter] = $value;
4✔
833
            }
834
        }
835
    }
836

837
    protected function getDefaultValueByType($type)
838
    {
839
        $values = [
3✔
840
            'object' => 'null',
3✔
841
            'boolean' => false,
3✔
842
            'date' => "0000-00-00",
3✔
843
            'integer' => 0,
3✔
844
            'string' => '',
3✔
845
            'double' => 0
3✔
846
        ];
3✔
847

848
        return $values[$type];
3✔
849
    }
850

851
    protected function prepareInfo(array $info): array
852
    {
853
        if (empty($info)) {
18✔
854
            return $info;
1✔
855
        }
856

857
        foreach ($info['license'] as $key => $value) {
17✔
858
            if (empty($value)) {
17✔
859
                unset($info['license'][$key]);
17✔
860
            }
861
        }
862

863
        if (empty($info['license'])) {
17✔
864
            unset($info['license']);
17✔
865
        }
866

867
        if (!empty($info['description'])) {
17✔
868
            $info['description'] = view($info['description'])->render();
17✔
869
        }
870

871
        return $info;
17✔
872
    }
873

874
    protected function validateSpec(array $doc): void
875
    {
876
        app(SwaggerSpecValidator::class)->validate($doc);
54✔
877
    }
878
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc