diff --git a/src/Http/Middleware/AutoDocMiddleware.php b/src/Http/Middleware/AutoDocMiddleware.php index a048448..e047211 100644 --- a/src/Http/Middleware/AutoDocMiddleware.php +++ b/src/Http/Middleware/AutoDocMiddleware.php @@ -16,7 +16,7 @@ public function handle($request, Closure $next) { $response = $next($request); - if ((config('app.env') == 'testing') && !self::$skipped && !empty($request->route())) { + if ((config('app.env') === 'testing') && !self::$skipped && !empty($request->route())) { app(SwaggerService::class)->addData($request, $response); } diff --git a/src/Services/SwaggerService.php b/src/Services/SwaggerService.php index c4da9e5..512565b 100755 --- a/src/Services/SwaggerService.php +++ b/src/Services/SwaggerService.php @@ -69,7 +69,7 @@ public function __construct(Container $container) $this->setDriver(); - if (config('app.env') == 'testing') { + if (config('app.env') === 'testing') { $this->container = $container; $this->security = $this->config['security']; @@ -281,6 +281,60 @@ protected function markAsDeprecated(array $annotations) $this->item['deprecated'] = Arr::get($annotations, 'deprecated', false); } + protected function saveResponseSchema(?array $content, string $definition): void + { + if (empty($content)) { + return; + } + + $schemaProperties = []; + $schemaType = 'object'; + + if (array_is_list($content)) { + $this->saveListResponseDefinitions($content, $schemaProperties); + + $schemaType = 'array'; + } else { + $this->saveObjectResponseDefinitions($content, $schemaProperties, $definition); + } + + $this->data['definitions'][$definition] = [ + 'type' => $schemaType, + 'properties' => $schemaProperties + ]; + } + + protected function saveListResponseDefinitions(array $content, array &$schemaProperties): void + { + $types = []; + + foreach ($content as $value) { + $type = gettype($value); + + if (!in_array($type, $types)) { + $types[] = $type; + $schemaProperties['items']['allOf'][]['type'] = $type; + } + } + } + + protected function saveObjectResponseDefinitions(array $content, array &$schemaProperties, string $definition): void + { + $properties = Arr::get($this->data['definitions'], $definition, []); + + foreach ($content as $name => $value) { + $property = Arr::get($properties, $name, []); + + if (is_null($value)) { + $property['nullable'] = true; + } else { + $property['type'] = gettype($value); + } + + $schemaProperties[$name] = $property; + } + } + protected function parseResponse($response) { $produceList = $this->data['paths'][$this->uri][$this->method]['produces']; @@ -325,6 +379,15 @@ protected function parseResponse($response) $produce ); } + + $action = Str::ucfirst($this->getActionName($this->uri)); + $definition = "{$this->method}{$action}{$code}ResponseObject"; + + $this->saveResponseSchema($content, $definition); + + if (is_array($this->item['responses'][$code])) { + $this->item['responses'][$code]['schema']['$ref'] = "#/definitions/{$definition}"; + } } protected function saveExample($code, $content, $produce) @@ -424,7 +487,7 @@ protected function saveGetRequestParameters($rules, array $attributes, array $an } $existedParameter = Arr::first($this->item['parameters'], function ($existedParameter) use ($parameter) { - return $existedParameter['name'] == $parameter; + return $existedParameter['name'] === $parameter; }); if (empty($existedParameter)) { @@ -542,7 +605,7 @@ protected function requestHasBody(): bool $parameters = $this->data['paths'][$this->uri][$this->method]['parameters']; $bodyParamExisted = Arr::where($parameters, function ($value) { - return $value['name'] == 'body'; + return $value['name'] === 'body'; }); return empty($bodyParamExisted); @@ -552,7 +615,7 @@ public function getConcreteRequest() { $controller = $this->request->route()->getActionName(); - if ($controller == 'Closure') { + if ($controller === 'Closure') { return null; } @@ -735,7 +798,7 @@ protected function camelCaseToUnderScore($input): string $ret = $matches[0]; foreach ($ret as &$match) { - $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match); + $match = ($match === strtoupper($match)) ? strtolower($match) : lcfirst($match); } return implode('_', $ret); diff --git a/tests/fixtures/AutoDocMiddlewareTest/tmp_data_search_roles_request.json b/tests/fixtures/AutoDocMiddlewareTest/tmp_data_search_roles_request.json index d97b41b..c7e70b6 100644 --- a/tests/fixtures/AutoDocMiddlewareTest/tmp_data_search_roles_request.json +++ b/tests/fixtures/AutoDocMiddlewareTest/tmp_data_search_roles_request.json @@ -65,7 +65,8 @@ } ] } - ] + ], + "$ref": "#/definitions/getUsersroles200ResponseObject" } } }, @@ -76,7 +77,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_create_user_request.json b/tests/fixtures/SwaggerServiceTest/tmp_data_create_user_request.json index f4c8403..6b8b857 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_create_user_request.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_create_user_request.json @@ -30,6 +30,7 @@ "description": "Forbidden", "schema": { + "$ref": "#/definitions/postApiusers403ResponseObject", "example": { "message": "This action is unauthorized." @@ -68,6 +69,14 @@ "first_name": "andrey", "last_name": "voronin" } + }, + "postApiusers403ResponseObject": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } } }, "info": diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request.json b/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request.json index b50eb24..d596add 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_get_user_request.json @@ -60,7 +60,8 @@ "id": 2, "name": "client" } - } + }, + "$ref": "#/definitions/getUsers{id}assignRole{roleId}200ResponseObject" } } }, @@ -71,7 +72,25 @@ } } }, - "definitions": {}, + "definitions": { + "getUsers{id}assignRole{roleId}200ResponseObject": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "likes_count": { + "type": "integer" + }, + "role": { + "type": "array" + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_post_user_request.json b/tests/fixtures/SwaggerServiceTest/tmp_data_post_user_request.json index b32fb45..4b06ca3 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_post_user_request.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_post_user_request.json @@ -55,7 +55,8 @@ } ] } - ] + ], + "$ref": "#/definitions/postUsers200ResponseObject" } } }, @@ -97,6 +98,18 @@ "required": [ "query" ] + }, + "postUsers200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } } }, "info": { diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_post_user_request_with_object_params.json b/tests/fixtures/SwaggerServiceTest/tmp_data_post_user_request_with_object_params.json index f3ba852..8951d69 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_post_user_request_with_object_params.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_post_user_request_with_object_params.json @@ -53,7 +53,8 @@ } ] } - ] + ], + "$ref": "#/definitions/postUsers200ResponseObject" } } }, @@ -93,6 +94,18 @@ "notification_settings": "RonasIT\\Support\\Tests\\Support\\Mock\\TestNotificationSetting" }, "required": ["query"] + }, + "postUsers200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } } }, "info": { diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_put_user_request_with_early_generated_doc.json b/tests/fixtures/SwaggerServiceTest/tmp_data_put_user_request_with_early_generated_doc.json index 25aef98..d0b96c6 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_put_user_request_with_early_generated_doc.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_put_user_request_with_early_generated_doc.json @@ -103,7 +103,8 @@ "204": { "description": "Operation successfully done", "schema": { - "example": null + "example": null, + "$ref": "#/definitions/patchUsers{id}204ResponseObject" } } }, diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_closure_request.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_closure_request.json index 29ca2f1..0bcfd57 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_closure_request.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_closure_request.json @@ -43,7 +43,8 @@ } ] } - ] + ], + "$ref": "#/definitions/getUsersroles200ResponseObject" } } }, @@ -52,7 +53,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request.json index cd42ead..b6176e5 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request.json @@ -65,7 +65,8 @@ } ] } - ] + ], + "$ref": "#/definitions/getUsersroles200ResponseObject" } } }, @@ -76,7 +77,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_invalid_content_type.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_invalid_content_type.json index f59626a..7c618ae 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_invalid_content_type.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_invalid_content_type.json @@ -46,7 +46,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_jwt_security.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_jwt_security.json index 5060a32..c50c547 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_jwt_security.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_jwt_security.json @@ -65,7 +65,8 @@ } ] } - ] + ], + "$ref": "#/definitions/getUsersroles200ResponseObject" } } }, @@ -76,7 +77,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_laravel_security.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_laravel_security.json index d3f3cb7..ffd79a9 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_laravel_security.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_laravel_security.json @@ -65,7 +65,8 @@ } ] } - ] + ], + "$ref": "#/definitions/getUsersroles200ResponseObject" } } }, @@ -76,7 +77,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_pdf.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_pdf.json index 03e4c89..b725c94 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_pdf.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_pdf.json @@ -40,7 +40,8 @@ "200": { "description": "Operation successfully done", "schema": { - "example": "WwogICAgewogICAgICAgICJpZCI6IDEsCiAgICAgICAgIm5hbWUiOiAiYWRtaW4iLAogICAgICAgICJ1c2VycyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkIjogMSwKICAgICAgICAgICAgICAgICJuYW1lIjogImFkbWluIgogICAgICAgICAgICB9CiAgICAgICAgXQogICAgfSwKICAgIHsKICAgICAgICAiaWQiOiAyLAogICAgICAgICJuYW1lIjogImNsaWVudCIsCiAgICAgICAgInVzZXJzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWQiOiAyLAogICAgICAgICAgICAgICAgIm5hbWUiOiAiZmlyc3RfY2xpZW50IgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWQiOiAzLAogICAgICAgICAgICAgICAgIm5hbWUiOiAic2Vjb25kX2NsaWVudCIKICAgICAgICAgICAgfQogICAgICAgIF0KICAgIH0KXQ==" + "example": "WwogICAgewogICAgICAgICJpZCI6IDEsCiAgICAgICAgIm5hbWUiOiAiYWRtaW4iLAogICAgICAgICJ1c2VycyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgImlkIjogMSwKICAgICAgICAgICAgICAgICJuYW1lIjogImFkbWluIgogICAgICAgICAgICB9CiAgICAgICAgXQogICAgfSwKICAgIHsKICAgICAgICAiaWQiOiAyLAogICAgICAgICJuYW1lIjogImNsaWVudCIsCiAgICAgICAgInVzZXJzIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWQiOiAyLAogICAgICAgICAgICAgICAgIm5hbWUiOiAiZmlyc3RfY2xpZW50IgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiaWQiOiAzLAogICAgICAgICAgICAgICAgIm5hbWUiOiAic2Vjb25kX2NsaWVudCIKICAgICAgICAgICAgfQogICAgICAgIF0KICAgIH0KXQ==", + "$ref": "#/definitions/getUsersroles200ResponseObject" } } }, @@ -51,7 +52,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_plain_text.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_plain_text.json index ebc72a6..1bd8547 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_plain_text.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_plain_text.json @@ -41,6 +41,9 @@ "description": "Operation successfully done", "examples": { "example": "[\n {\n \"id\": 1,\n \"name\": \"admin\",\n \"users\": [\n {\n \"id\": 1,\n \"name\": \"admin\"\n }\n ]\n }\n]" + }, + "schema": { + "$ref": "#/definitions/getUsersroles200ResponseObject" } } }, @@ -51,7 +54,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_with_annotations.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_with_annotations.json index 7cbad83..98acde9 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_with_annotations.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_with_annotations.json @@ -53,7 +53,8 @@ } ] } - ] + ], + "$ref": "#/definitions/getUsersroles200ResponseObject" } } }, @@ -64,7 +65,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_without_rule_type.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_without_rule_type.json index 4e7e533..1c62bab 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_without_rule_type.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_roles_request_without_rule_type.json @@ -53,7 +53,8 @@ } ] } - ] + ], + "$ref": "#/definitions/getUsersroles200ResponseObject" } } }, @@ -64,7 +65,20 @@ } } }, - "definitions": {}, + "definitions": { + "getUsersroles200ResponseObject": { + "type": "array", + "properties": { + "items": { + "allOf": [ + { + "type": "array" + } + ] + } + } + } + }, "info": { "description": "This is automatically collected documentation", "version": "0.0.0", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_users_empty_request.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_users_empty_request.json index 3ff508f..6182997 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_users_empty_request.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_users_empty_request.json @@ -69,7 +69,8 @@ "prev_page_url": null, "to": 1, "total": 1 - } + }, + "$ref": "#/definitions/getApiusers200ResponseObject" } } }, @@ -79,7 +80,52 @@ } } }, - "definitions": [], + "definitions": { + "getApiusers200ResponseObject": { + "type": "object", + "properties": { + "current_page": { + "type": "integer" + }, + "data": { + "type": "array" + }, + "first_page_url": { + "type": "string" + }, + "from": { + "type": "integer" + }, + "last_page": { + "type": "integer" + }, + "last_page_url": { + "type": "string" + }, + "links": { + "type": "array" + }, + "next_page_url": { + "nullable": true + }, + "path": { + "type": "string" + }, + "per_page": { + "type": "integer" + }, + "prev_page_url": { + "nullable": true + }, + "to": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + } + }, "info": { "description": "This is automatically collected documentation", diff --git a/tests/fixtures/SwaggerServiceTest/tmp_data_search_users_request.json b/tests/fixtures/SwaggerServiceTest/tmp_data_search_users_request.json index c3a6218..943c46a 100644 --- a/tests/fixtures/SwaggerServiceTest/tmp_data_search_users_request.json +++ b/tests/fixtures/SwaggerServiceTest/tmp_data_search_users_request.json @@ -89,7 +89,8 @@ "prev_page_url": null, "to": 1, "total": 1 - } + }, + "$ref": "#/definitions/getApiusers200ResponseObject" } } }, @@ -101,7 +102,52 @@ } } }, - "definitions": [], + "definitions": { + "getApiusers200ResponseObject": { + "type": "object", + "properties": { + "current_page": { + "type": "integer" + }, + "data": { + "type": "array" + }, + "first_page_url": { + "type": "string" + }, + "from": { + "type": "integer" + }, + "last_page": { + "type": "integer" + }, + "last_page_url": { + "type": "string" + }, + "links": { + "type": "array" + }, + "next_page_url": { + "nullable": true + }, + "path": { + "type": "string" + }, + "per_page": { + "type": "integer" + }, + "prev_page_url": { + "nullable": true + }, + "to": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + } + }, "info": { "description": "This is automatically collected documentation",