Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#45: Add swagger spec validator to validate temp and additional doc format #82

Merged
merged 28 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
65982dc
feat: added swagger format validation when getting tmp doc file and g…
vitgrams Apr 5, 2023
25dcf9a
feat: improved validation mechanism and moved spec validation logic i…
vitgrams Apr 9, 2023
c46872d
fix: minor fixes;
vitgrams Apr 12, 2023
801549a
tests: added new test cases;
vitgrams Apr 14, 2023
30a7710
fix: minor fixes;
vitgrams Apr 17, 2023
da20b81
style: Format code with php-cs-fixer
deepsource-autofix[bot] Apr 17, 2023
9450fad
fix: fixed refs validation;
vitgrams Apr 18, 2023
b057d01
fix: fixed composer.lock;
vitgrams Apr 18, 2023
a1db0a2
fix: minor fixes;
vitgrams Apr 19, 2023
371a82c
fix: fixed code style, added test cases;
vitgrams Apr 24, 2023
1699855
style: fix code-style issue
Goodmain Sep 24, 2023
29b4d52
style: format code with PHP CS Fixer
deepsource-autofix[bot] Sep 24, 2023
bd9bdaa
Merge branch 'master' into 45_fix_exception_when_setting_invalid_addi…
Goodmain Sep 24, 2023
f94003a
Merge remote-tracking branch 'origin/45_fix_exception_when_setting_in…
Goodmain Sep 24, 2023
e00022f
test: fix tests after merge
Goodmain Sep 25, 2023
1d8a9cb
style: fix code-style issues
Goodmain Sep 25, 2023
de6fefb
style: format code with PHP CS Fixer
deepsource-autofix[bot] Sep 25, 2023
b672b96
style: fix code-style issues
Goodmain Sep 25, 2023
2b05267
Merge remote-tracking branch 'origin/45_fix_exception_when_setting_in…
Goodmain Sep 25, 2023
1b7fb35
test: increase code coverage
Goodmain Sep 25, 2023
1ef59da
refactor: remove not necessary function
Goodmain Sep 25, 2023
9ec90bf
style: fix code-style issues
Goodmain Sep 25, 2023
7a4c281
test: increase code coverage
Goodmain Sep 26, 2023
7d65f18
refactor: change code according to the comments
Goodmain Sep 27, 2023
4e1adb8
refactor: change code according to the comments
Goodmain Sep 27, 2023
f2503a9
refactor: change code according to the comments
Goodmain Sep 28, 2023
1e5a1fc
Update tests/SwaggerServiceTest.php
DenTray Oct 2, 2023
9b84277
Update tests/SwaggerServiceTest.php
DenTray Oct 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"require": {
"php": ">=7.3.0",
"laravel/framework": ">=5.3.0",
"phpunit/phpunit": ">=7.0"
"phpunit/phpunit": ">=7.0",
"ronasit/laravel-helpers": "~2.1.0"
DenTray marked this conversation as resolved.
Show resolved Hide resolved
},
"require-dev": {
"orchestra/testbench": "^6.25",
Expand Down
2,684 changes: 1,682 additions & 1,002 deletions composer.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion config/auto-doc.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
| Documentation Template
|--------------------------------------------------------------------------
|
| You can use your custom documentation view
| You can use your custom documentation view. Title and version fields are required.
DenTray marked this conversation as resolved.
Show resolved Hide resolved
DenTray marked this conversation as resolved.
Show resolved Hide resolved
*/
'description' => 'auto-doc::swagger-description',
'version' => '0.0.0',
Expand Down
13 changes: 13 additions & 0 deletions src/Exceptions/SpecValidation/DuplicateFieldException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class DuplicateFieldException extends InvalidSwaggerSpecException
{
public function __construct(string $fieldName, array $fieldValue)
{
$fieldValueString = implode(', ', $fieldValue);

parent::__construct("Validation failed. Found multiple fields '{$fieldName}' with values: {$fieldValueString}.");
DenTray marked this conversation as resolved.
Show resolved Hide resolved
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/SpecValidation/DuplicateParamException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class DuplicateParamException extends InvalidSwaggerSpecException
{
public function __construct(string $in, string $name, string $operationId)
{
parent::__construct("Validation failed. Operation '{$operationId}' has multiple in:{$in} parameters with name:{$name}.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class DuplicatePathPlaceholderException extends InvalidSwaggerSpecException
{
public function __construct(array $placeholders, string $path)
{
$placeholdersString = implode(', ', $placeholders);

parent::__construct("Validation failed. Path '{$path}' has multiple path placeholders with name: {$placeholdersString}.");
}
}
13 changes: 13 additions & 0 deletions src/Exceptions/SpecValidation/InvalidFieldValueException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class InvalidFieldValueException extends InvalidSwaggerSpecException
{
public function __construct(string $fieldId, array $allowedValues)
DenTray marked this conversation as resolved.
Show resolved Hide resolved
{
$allowedValuesString = implode(', ', $allowedValues);

parent::__construct("Validation failed. Field '{$fieldId}' has an invalid value. Allowed values: {$allowedValuesString}.");
DenTray marked this conversation as resolved.
Show resolved Hide resolved
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/SpecValidation/InvalidPathException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class InvalidPathException extends InvalidSwaggerSpecException
{
public function __construct(string $path)
{
parent::__construct("Validation failed at '{$path}'. Paths should only have path names that starts with `/`.");
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/SpecValidation/InvalidStatusCodeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class InvalidStatusCodeException extends InvalidSwaggerSpecException
{
public function __construct(string $responseId)
{
parent::__construct("Validation failed at '{$responseId}'. Operation should only have three-digit status codes, `default`, and vendor extensions (`x-*`) as properties.");
}
}
9 changes: 9 additions & 0 deletions src/Exceptions/SpecValidation/InvalidSwaggerSpecException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

use Exception;

class InvalidSwaggerSpecException extends Exception
{
}
11 changes: 11 additions & 0 deletions src/Exceptions/SpecValidation/InvalidSwaggerVersionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class InvalidSwaggerVersionException extends InvalidSwaggerSpecException
{
public function __construct(string $version)
{
parent::__construct("Unrecognized Swagger version '{$version}'. Expected 2.0.");
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/SpecValidation/MissingExternalRefException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class MissingExternalRefException extends InvalidSwaggerSpecException
{
public function __construct(string $ref, string $filename)
{
parent::__construct("Validation failed. Ref '{$ref}' is used in \$ref but not defined in '{$filename}' file.");
}
}
13 changes: 13 additions & 0 deletions src/Exceptions/SpecValidation/MissingFieldException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class MissingFieldException extends InvalidSwaggerSpecException
{
public function __construct(array $missingFields, string $parentField = null)
{
$fieldsString = implode(', ', $missingFields);

parent::__construct("Validation failed. '{$parentField}' should have required fields: {$fieldsString}.");
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/SpecValidation/MissingLocalRefException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class MissingLocalRefException extends InvalidSwaggerSpecException
{
public function __construct(string $ref, string $parentField)
{
parent::__construct("Validation failed. Ref '{$ref}' is used in \$ref but not defined in '{$parentField}' field.");
}
}
13 changes: 13 additions & 0 deletions src/Exceptions/SpecValidation/MissingPathParamException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class MissingPathParamException extends InvalidSwaggerSpecException
{
public function __construct(string $operationId, array $placeholders)
{
$placeholdersString = implode(', ', $placeholders);

parent::__construct("Validation failed. Operation '{$operationId}' has no params for placeholders: {$placeholdersString}.");
}
}
13 changes: 13 additions & 0 deletions src/Exceptions/SpecValidation/MissingPathPlaceholderException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class MissingPathPlaceholderException extends InvalidSwaggerSpecException
{
public function __construct(string $operationId, array $params)
{
$paramsString = implode(', ', $params);

parent::__construct("Validation failed. Operation '{$operationId}' has no placeholders for params: {$paramsString}.");
}
}
11 changes: 11 additions & 0 deletions src/Exceptions/SpecValidation/MissingRefFileException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace RonasIT\Support\AutoDoc\Exceptions\SpecValidation;

class MissingRefFileException extends InvalidSwaggerSpecException
{
public function __construct(string $filename)
{
parent::__construct("Validation failed. Filename '{$filename}' is used in \$ref but file doesn't exist.");
}
}
106 changes: 59 additions & 47 deletions src/Services/SwaggerService.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
use ReflectionClass;
use RonasIT\Support\AutoDoc\Exceptions\InvalidDriverClassException;
use RonasIT\Support\AutoDoc\Exceptions\LegacyConfigException;
use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\InvalidSwaggerSpecException;
use RonasIT\Support\AutoDoc\Exceptions\SpecValidation\InvalidSwaggerVersionException;
use RonasIT\Support\AutoDoc\Exceptions\SwaggerDriverClassNotFoundException;
use RonasIT\Support\AutoDoc\Exceptions\WrongSecurityConfigException;
use RonasIT\Support\AutoDoc\Interfaces\SwaggerDriverInterface;
use RonasIT\Support\AutoDoc\Traits\GetDependenciesTrait;
use RonasIT\Support\AutoDoc\Validators\SwaggerSpecValidator;
use Symfony\Component\HttpFoundation\Response;

/**
Expand Down Expand Up @@ -61,7 +64,9 @@ public function __construct(Container $container)

$this->data = $this->driver->getTmpData();

if (empty($this->data)) {
if (!empty($this->data)) {
$this->validateSpec($this->data);
} else {
$this->data = $this->generateEmptyData();

$this->driver->saveTmpData($this->data);
Expand All @@ -84,6 +89,10 @@ protected function initConfig()
if (version_compare($packageConfigs['config_version'], $version, '>')) {
throw new LegacyConfigException();
}

if (version_compare($this->config['swagger']['version'], '2.0', '!=')) {
DenTray marked this conversation as resolved.
Show resolved Hide resolved
throw new InvalidSwaggerVersionException($this->config['swagger']['version']);
}
}

protected function setDriver()
Expand All @@ -105,49 +114,38 @@ protected function setDriver()
protected function generateEmptyData(): array
{
$data = [
'swagger' => Arr::get($this->config, 'swagger.version'),
'swagger' => Arr::get($this->config, 'swagger.version', '2.0'),
DenTray marked this conversation as resolved.
Show resolved Hide resolved
'host' => $this->getAppUrl(),
'basePath' => $this->config['basePath'],
'schemes' => $this->config['schemes'],
'paths' => [],
'definitions' => $this->config['definitions']
'definitions' => $this->config['definitions'],
'info' => $this->prepareInfo($this->config['info'])
];

$info = $this->prepareInfo($this->config['info']);

if (!empty($info)) {
$data['info'] = $info;
}

$securityDefinitions = $this->generateSecurityDefinition();

if (!empty($securityDefinitions)) {
$data['securityDefinitions'] = $securityDefinitions;
}

if (!empty($data['info']['description'])) {
$data['info']['description'] = view($data['info']['description'])->render();
}

return $data;
}

protected function getAppUrl()
protected function getAppUrl(): string
{
$url = config('app.url');

return str_replace(['http://', 'https://', '/'], '', $url);
}

protected function generateSecurityDefinition()
protected function generateSecurityDefinition(): ?array
{
if (empty($this->security)) {
return '';
}

return [
$this->security => $this->generateSecurityDefinitionObject($this->security)
];
return (empty($this->security))
? null
: [
$this->security => $this->generateSecurityDefinitionObject($this->security)
];
DenTray marked this conversation as resolved.
Show resolved Hide resolved
}

protected function generateSecurityDefinitionObject($type): array
Expand Down Expand Up @@ -653,30 +651,40 @@ public function getDocFileContent()
if (file_exists($fullFilePath)) {
DenTray marked this conversation as resolved.
Show resolved Hide resolved
$fileContent = json_decode(file_get_contents($fullFilePath), true);

$paths = array_keys($fileContent['paths']);
if ($fileContent) {
DenTray marked this conversation as resolved.
Show resolved Hide resolved
try {
$this->validateSpec($fileContent);
} catch (InvalidSwaggerSpecException $exception) {
report($exception);

continue;
}

$paths = array_keys($fileContent['paths']);

foreach ($paths as $path) {
$additionalDocPath = $fileContent['paths'][$path];
foreach ($paths as $path) {
$additionalDocPath = $fileContent['paths'][$path];

if (empty($documentation['paths'][$path])) {
$documentation['paths'][$path] = $additionalDocPath;
} else {
$methods = array_keys($documentation['paths'][$path]);
$additionalDocMethods = array_keys($additionalDocPath);
if (empty($documentation['paths'][$path])) {
$documentation['paths'][$path] = $additionalDocPath;
} else {
$methods = array_keys($documentation['paths'][$path]);
$additionalDocMethods = array_keys($additionalDocPath);

foreach ($additionalDocMethods as $method) {
if (!in_array($method, $methods)) {
$documentation['paths'][$path][$method] = $additionalDocPath[$method];
foreach ($additionalDocMethods as $method) {
if (!in_array($method, $methods)) {
$documentation['paths'][$path][$method] = $additionalDocPath[$method];
}
}
}
}
}

$definitions = array_keys($fileContent['definitions']);
$definitions = array_keys($fileContent['definitions']);

foreach ($definitions as $definition) {
if (empty($documentation['definitions'][$definition])) {
$documentation['definitions'][$definition] = $fileContent['definitions'][$definition];
foreach ($definitions as $definition) {
if (empty($documentation['definitions'][$definition])) {
$documentation['definitions'][$definition] = $fileContent['definitions'][$definition];
}
}
}
}
Expand Down Expand Up @@ -787,25 +795,29 @@ protected function getDefaultValueByType($type)
return $values[$type];
}

/**
* @param $info
* @return mixed
*/
protected function prepareInfo($info)
protected function prepareInfo(array $info): array
{
if (empty($info)) {
return $info;
}

foreach ($info['license'] as $key => $value) {
if (empty($value)) {
unset($info['license'][$key]);
}
}

if (empty($info['license'])) {
unset($info['license']);
}

if (!empty($info['description'])) {
$info['description'] = view($info['description'])->render();
}

return $info;
}

protected function validateSpec(array $doc): void
{
$validator = new SwaggerSpecValidator($doc);

$validator->validate();
DenTray marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading