From 92c86eea90cd8373ea9c8866e45aad19dafcf77a Mon Sep 17 00:00:00 2001 From: dpankratov Date: Sat, 28 Oct 2023 12:54:48 +0600 Subject: [PATCH 1/3] refactor: disable open api spec validation on main documentation in constructor --- src/Services/SwaggerService.php | 96 ++++++++++--------- tests/SwaggerServiceTest.php | 7 +- .../Traits/SwaggerServiceMockTrait.php | 12 +++ 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/src/Services/SwaggerService.php b/src/Services/SwaggerService.php index e6e4dc14..4e5c79ae 100755 --- a/src/Services/SwaggerService.php +++ b/src/Services/SwaggerService.php @@ -31,6 +31,7 @@ class SwaggerService public const SWAGGER_VERSION = '2.0'; protected $driver; + protected $openAPIValidator; protected $data; protected $config; @@ -57,6 +58,8 @@ class SwaggerService public function __construct(Container $container) { + $this->openAPIValidator = app(SwaggerSpecValidator::class); + $this->initConfig(); $this->setDriver(); @@ -68,9 +71,7 @@ public function __construct(Container $container) $this->data = $this->driver->getTmpData(); - if (!empty($this->data)) { - $this->validateSpec($this->data); - } else { + if (empty($this->data)) { $this->data = $this->generateEmptyData(); $this->driver->saveTmpData($this->data); @@ -692,55 +693,20 @@ public function getDocFileContent() { $documentation = $this->driver->getDocumentation(); + $this->openAPIValidator->validate($documentation); + $additionalDocs = config('auto-doc.additional_paths', []); foreach ($additionalDocs as $filePath) { - $fullFilePath = base_path($filePath); - try { - if (!file_exists($fullFilePath)) { - throw new DocFileNotExistsException($fullFilePath); - } - - $fileContent = json_decode(file_get_contents($fullFilePath), true); - - if (empty($fileContent)) { - throw new EmptyDocFileException($fullFilePath); - } - - $this->validateSpec($fileContent); + $additionalDocContent = $this->getOpenAPIFileContent(base_path($filePath)); } catch (DocFileNotExistsException|EmptyDocFileException|InvalidSwaggerSpecException $exception) { report($exception); continue; } - $paths = array_keys($fileContent['paths']); - - 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); - - foreach ($additionalDocMethods as $method) { - if (!in_array($method, $methods)) { - $documentation['paths'][$path][$method] = $additionalDocPath[$method]; - } - } - } - } - - $definitions = array_keys($fileContent['definitions']); - - foreach ($definitions as $definition) { - if (empty($documentation['definitions'][$definition])) { - $documentation['definitions'][$definition] = $fileContent['definitions'][$definition]; - } - } + $this->mergeOpenAPIDocs($documentation, $additionalDocContent); } return $documentation; @@ -871,8 +837,50 @@ protected function prepareInfo(array $info): array return $info; } - protected function validateSpec(array $doc): void + protected function getOpenAPIFileContent(string $filePath): array + { + if (!file_exists($filePath)) { + throw new DocFileNotExistsException($filePath); + } + + $fileContent = json_decode(file_get_contents($filePath), true); + + if (empty($fileContent)) { + throw new EmptyDocFileException($filePath); + } + + $this->openAPIValidator->validate($fileContent); + + return $fileContent; + } + + protected function mergeOpenAPIDocs(array &$documentation, array $additionalDocumentation): void { - app(SwaggerSpecValidator::class)->validate($doc); + $paths = array_keys($additionalDocumentation['paths']); + + foreach ($paths as $path) { + $additionalDocPath = $additionalDocumentation['paths'][$path]; + + 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]; + } + } + } + } + + $definitions = array_keys($additionalDocumentation['definitions']); + + foreach ($definitions as $definition) { + if (empty($documentation['definitions'][$definition])) { + $documentation['definitions'][$definition] = $additionalDocumentation['definitions'][$definition]; + } + } } } diff --git a/tests/SwaggerServiceTest.php b/tests/SwaggerServiceTest.php index 8e2c9061..b4d98f08 100755 --- a/tests/SwaggerServiceTest.php +++ b/tests/SwaggerServiceTest.php @@ -281,13 +281,14 @@ public function getConstructorInvalidTmpData(): array * @param string $exception * @param string $exceptionMessage */ - public function testConstructorInvalidTmpData(string $tmpDoc, string $exception, string $exceptionMessage) + public function testGetDocFileContentInvalidTmpData(string $docFilePath, string $exception, string $exceptionMessage) { - $this->mockDriverGetTpmData($this->getJsonFixture($tmpDoc)); + $this->mockDriverGetDocumentation($this->getJsonFixture($docFilePath)); + $this->expectException($exception); $this->expectExceptionMessage($exceptionMessage); - app(SwaggerService::class); + app(SwaggerService::class)->getDocFileContent(); } public function testEmptyContactEmail() diff --git a/tests/support/Traits/SwaggerServiceMockTrait.php b/tests/support/Traits/SwaggerServiceMockTrait.php index d54df43c..b61d4d59 100644 --- a/tests/support/Traits/SwaggerServiceMockTrait.php +++ b/tests/support/Traits/SwaggerServiceMockTrait.php @@ -60,4 +60,16 @@ protected function mockDriverGetTpmData($tmpData, $driverClass = LocalDriver::cl $this->app->instance($driverClass, $driver); } + + protected function mockDriverGetDocumentation($data, $driverClass = LocalDriver::class) + { + $driver = $this->mockClass($driverClass, ['getDocumentation']); + + $driver + ->expects($this->exactly(1)) + ->method('getDocumentation') + ->willReturn($data); + + $this->app->instance($driverClass, $driver); + } } From 58b1b8f236cdc87d6aa268a6d8d1ad50bbdd95ed Mon Sep 17 00:00:00 2001 From: dpankratov Date: Sat, 28 Oct 2023 12:56:31 +0600 Subject: [PATCH 2/3] chore: rename tmp var --- tests/SwaggerServiceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/SwaggerServiceTest.php b/tests/SwaggerServiceTest.php index b4d98f08..24698167 100755 --- a/tests/SwaggerServiceTest.php +++ b/tests/SwaggerServiceTest.php @@ -277,7 +277,7 @@ public function getConstructorInvalidTmpData(): array /** * @dataProvider getConstructorInvalidTmpData * - * @param string $tmpDoc + * @param string $docFilePath * @param string $exception * @param string $exceptionMessage */ From 80b9853e2ed45540a6aef1bcdfa337f1efca91df Mon Sep 17 00:00:00 2001 From: dpankratov Date: Sat, 28 Oct 2023 17:24:23 +0600 Subject: [PATCH 3/3] tests: cover uncovered security definitions validation --- src/Validators/SwaggerSpecValidator.php | 6 +- tests/SwaggerServiceTest.php | 15 ++++ ...lid_format__security_definition__flow.json | 88 +++++++++++++++++++ ...valid_format__security_definition__in.json | 88 +++++++++++++++++++ ...lid_format__security_definition__type.json | 88 +++++++++++++++++++ 5 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__flow.json create mode 100644 tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__in.json create mode 100644 tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__type.json diff --git a/src/Validators/SwaggerSpecValidator.php b/src/Validators/SwaggerSpecValidator.php index 86df624e..968a4193 100644 --- a/src/Validators/SwaggerSpecValidator.php +++ b/src/Validators/SwaggerSpecValidator.php @@ -155,9 +155,9 @@ protected function validateSecurityDefinitions(): void $this->validateFieldsPresent(self::REQUIRED_FIELDS['security_definition'], $parentId); - $this->validateFieldValue("{$parentId}.'type", self::ALLOWED_VALUES['security_definition_type']); - $this->validateFieldValue("{$parentId}.'in", self::ALLOWED_VALUES['security_definition_in']); - $this->validateFieldValue("{$parentId}.'flow", self::ALLOWED_VALUES['security_definition_flow']); + $this->validateFieldValue("{$parentId}.type", self::ALLOWED_VALUES['security_definition_type']); + $this->validateFieldValue("{$parentId}.in", self::ALLOWED_VALUES['security_definition_in']); + $this->validateFieldValue("{$parentId}.flow", self::ALLOWED_VALUES['security_definition_flow']); } } diff --git a/tests/SwaggerServiceTest.php b/tests/SwaggerServiceTest.php index 24698167..0f8cddf2 100755 --- a/tests/SwaggerServiceTest.php +++ b/tests/SwaggerServiceTest.php @@ -271,6 +271,21 @@ public function getConstructorInvalidTmpData(): array 'exceptionMessage' => "Validation failed. Path parameters cannot be optional. " . "Set required=true for the 'username' parameters at operation 'paths./users.get'." ], + [ + 'tmpDoc' => 'documentation/invalid_format__security_definition__type', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. Field 'securityDefinitions.0.type' has an invalid value: invalid. Allowed values: basic, apiKey, oauth2." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__security_definition__flow', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. Field 'securityDefinitions.0.flow' has an invalid value: invalid. Allowed values: implicit, password, application, accessCode." + ], + [ + 'tmpDoc' => 'documentation/invalid_format__security_definition__in', + 'exception' => InvalidSwaggerSpecException::class, + 'exceptionMessage' => "Validation failed. Field 'securityDefinitions.0.in' has an invalid value: invalid. Allowed values: query, header." + ], ]; } diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__flow.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__flow.json new file mode 100644 index 00000000..dd8a1642 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__flow.json @@ -0,0 +1,88 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/api\/users": + { + "post": + { + "tags": ["api"], + "consumes": ["application\/x-www-form-urlencoded"], + "produces": ["application\/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#/definitions/apiusersObject" + } + } + ], + "responses": + { + "403": + { + "description": "Forbidden", + "schema": + { + "example": + { + "message": "This action is unauthorized." + } + } + } + }, + "security": [], + "description": "", + "summary": "test" + } + } + }, + "definitions": { + "apiusersObject": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "" + }, + "user_id": { + "type": "integer", + "description": "with_to_array_rule_string_name" + }, + "is_email_enabled": { + "type": "string", + "description": "test_rule_without_to_string" + } + }, + "required": { + "0": "query" + }, + "example": { + "first_name": "andrey", + "last_name": "voronin" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Name of Your Application", + "termsOfService": "", + "contact": + { + "email": "your@email.com" + } + }, + "securityDefinitions": [ + { + "type": "basic", + "in": "query", + "flow": "invalid" + } + ] +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__in.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__in.json new file mode 100644 index 00000000..e3ba0950 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__in.json @@ -0,0 +1,88 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/api\/users": + { + "post": + { + "tags": ["api"], + "consumes": ["application\/x-www-form-urlencoded"], + "produces": ["application\/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#/definitions/apiusersObject" + } + } + ], + "responses": + { + "403": + { + "description": "Forbidden", + "schema": + { + "example": + { + "message": "This action is unauthorized." + } + } + } + }, + "security": [], + "description": "", + "summary": "test" + } + } + }, + "definitions": { + "apiusersObject": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "" + }, + "user_id": { + "type": "integer", + "description": "with_to_array_rule_string_name" + }, + "is_email_enabled": { + "type": "string", + "description": "test_rule_without_to_string" + } + }, + "required": { + "0": "query" + }, + "example": { + "first_name": "andrey", + "last_name": "voronin" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Name of Your Application", + "termsOfService": "", + "contact": + { + "email": "your@email.com" + } + }, + "securityDefinitions": [ + { + "type": "basic", + "in": "invalid", + "flow": "password" + } + ] +} diff --git a/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__type.json b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__type.json new file mode 100644 index 00000000..ee6f9676 --- /dev/null +++ b/tests/fixtures/SwaggerServiceTest/documentation/invalid_format__security_definition__type.json @@ -0,0 +1,88 @@ +{ + "swagger": "2.0", + "host": "localhost", + "basePath": "\/", + "schemes": [], + "paths": { + "\/api\/users": + { + "post": + { + "tags": ["api"], + "consumes": ["application\/x-www-form-urlencoded"], + "produces": ["application\/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "", + "required": true, + "schema": { + "$ref": "#/definitions/apiusersObject" + } + } + ], + "responses": + { + "403": + { + "description": "Forbidden", + "schema": + { + "example": + { + "message": "This action is unauthorized." + } + } + } + }, + "security": [], + "description": "", + "summary": "test" + } + } + }, + "definitions": { + "apiusersObject": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "" + }, + "user_id": { + "type": "integer", + "description": "with_to_array_rule_string_name" + }, + "is_email_enabled": { + "type": "string", + "description": "test_rule_without_to_string" + } + }, + "required": { + "0": "query" + }, + "example": { + "first_name": "andrey", + "last_name": "voronin" + } + } + }, + "info": { + "description": "This is automatically collected documentation", + "version": "0.0.0", + "title": "Name of Your Application", + "termsOfService": "", + "contact": + { + "email": "your@email.com" + } + }, + "securityDefinitions": [ + { + "type": "invalid", + "in": "query", + "flow": "password" + } + ] +}