-
Notifications
You must be signed in to change notification settings - Fork 47
[feat]: update openapi version #134
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
Changes from all commits
4e3496f
c7d3fa4
efdcb59
b9a5983
4a8e6f9
c69c90b
601a8fa
fb18533
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -29,7 +29,7 @@ class SwaggerService | |||||||||||||||||
{ | ||||||||||||||||||
use GetDependenciesTrait; | ||||||||||||||||||
|
||||||||||||||||||
public const SWAGGER_VERSION = '2.0'; | ||||||||||||||||||
public const string OPEN_API_VERSION = '3.1.0'; | ||||||||||||||||||
|
||||||||||||||||||
protected $driver; | ||||||||||||||||||
protected $openAPIValidator; | ||||||||||||||||||
|
@@ -46,19 +46,19 @@ class SwaggerService | |||||||||||||||||
private $item; | ||||||||||||||||||
private $security; | ||||||||||||||||||
|
||||||||||||||||||
protected $ruleToTypeMap = [ | ||||||||||||||||||
protected array $ruleToTypeMap = [ | ||||||||||||||||||
'array' => 'object', | ||||||||||||||||||
'boolean' => 'boolean', | ||||||||||||||||||
'date' => 'date', | ||||||||||||||||||
'digits' => 'integer', | ||||||||||||||||||
'integer' => 'integer', | ||||||||||||||||||
'numeric' => 'double', | ||||||||||||||||||
'string' => 'string', | ||||||||||||||||||
'int' => 'integer' | ||||||||||||||||||
'int' => 'integer', | ||||||||||||||||||
]; | ||||||||||||||||||
|
||||||||||||||||||
protected $booleanAnnotations = [ | ||||||||||||||||||
'deprecated' | ||||||||||||||||||
'deprecated', | ||||||||||||||||||
]; | ||||||||||||||||||
|
||||||||||||||||||
public function __construct(Container $container) | ||||||||||||||||||
|
@@ -138,12 +138,14 @@ protected function generateEmptyData(): array | |||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
$data = [ | ||||||||||||||||||
'swagger' => self::SWAGGER_VERSION, | ||||||||||||||||||
'host' => $this->getAppUrl(), | ||||||||||||||||||
'basePath' => $this->config['basePath'], | ||||||||||||||||||
'schemes' => $this->config['schemes'], | ||||||||||||||||||
'openapi' => self::OPEN_API_VERSION, | ||||||||||||||||||
'servers' => [ | ||||||||||||||||||
['url' => $this->getAppUrl() . $this->config['basePath']], | ||||||||||||||||||
], | ||||||||||||||||||
'paths' => [], | ||||||||||||||||||
'definitions' => $this->config['definitions'], | ||||||||||||||||||
'components' => [ | ||||||||||||||||||
'schemas' => $this->config['definitions'], | ||||||||||||||||||
], | ||||||||||||||||||
'info' => $this->prepareInfo($this->config['info']) | ||||||||||||||||||
]; | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -242,7 +244,9 @@ protected function getPathParams(): array | |||||||||||||||||
'name' => $key, | ||||||||||||||||||
'description' => $this->generatePathDescription($key), | ||||||||||||||||||
'required' => true, | ||||||||||||||||||
'type' => 'string' | ||||||||||||||||||
'schema' => [ | ||||||||||||||||||
'type' => 'string' | ||||||||||||||||||
] | ||||||||||||||||||
]; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -307,7 +311,7 @@ protected function saveResponseSchema(?array $content, string $definition): void | |||||||||||||||||
$this->saveObjectResponseDefinitions($content, $schemaProperties, $definition); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
$this->data['definitions'][$definition] = [ | ||||||||||||||||||
$this->data['components']['schemas'][$definition] = [ | ||||||||||||||||||
'type' => $schemaType, | ||||||||||||||||||
'properties' => $schemaProperties | ||||||||||||||||||
]; | ||||||||||||||||||
|
@@ -329,7 +333,9 @@ protected function saveListResponseDefinitions(array $content, array &$schemaPro | |||||||||||||||||
|
||||||||||||||||||
protected function saveObjectResponseDefinitions(array $content, array &$schemaProperties, string $definition): void | ||||||||||||||||||
{ | ||||||||||||||||||
$properties = Arr::get($this->data['definitions'], $definition, []); | ||||||||||||||||||
$definitions = (!empty($this->data['components']['schemas'])) ? $this->data['components']['schemas'] : []; | ||||||||||||||||||
|
||||||||||||||||||
$properties = Arr::get($definitions, $definition, []); | ||||||||||||||||||
|
||||||||||||||||||
foreach ($content as $name => $value) { | ||||||||||||||||||
$property = Arr::get($properties, "properties.{$name}", []); | ||||||||||||||||||
|
@@ -395,7 +401,7 @@ protected function parseResponse($response) | |||||||||||||||||
$this->saveResponseSchema($content, $definition); | ||||||||||||||||||
|
||||||||||||||||||
if (is_array($this->item['responses'][$code])) { | ||||||||||||||||||
$this->item['responses'][$code]['schema']['$ref'] = "#/definitions/{$definition}"; | ||||||||||||||||||
$this->item['responses'][$code]['content'][$produce]['schema']['$ref'] = "#/components/schemas/{$definition}"; | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -418,17 +424,23 @@ protected function saveExample($code, $content, $produce) | |||||||||||||||||
|
||||||||||||||||||
protected function makeResponseExample($content, $mimeType, $description = ''): array | ||||||||||||||||||
{ | ||||||||||||||||||
$responseExample = ['description' => $description]; | ||||||||||||||||||
$example = match ($mimeType) { | ||||||||||||||||||
'application/json' => json_decode($content, true), | ||||||||||||||||||
'application/pdf' => base64_encode($content), | ||||||||||||||||||
default => $content, | ||||||||||||||||||
}; | ||||||||||||||||||
|
||||||||||||||||||
if ($mimeType === 'application/json') { | ||||||||||||||||||
$responseExample['schema'] = ['example' => json_decode($content, true)]; | ||||||||||||||||||
} elseif ($mimeType === 'application/pdf') { | ||||||||||||||||||
$responseExample['schema'] = ['example' => base64_encode($content)]; | ||||||||||||||||||
} else { | ||||||||||||||||||
$responseExample['examples']['example'] = $content; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
return $responseExample; | ||||||||||||||||||
return [ | ||||||||||||||||||
'description' => $description, | ||||||||||||||||||
'content' => [ | ||||||||||||||||||
$mimeType => [ | ||||||||||||||||||
'schema' => [ | ||||||||||||||||||
'type' => 'object', | ||||||||||||||||||
], | ||||||||||||||||||
'example' => $example, | ||||||||||||||||||
], | ||||||||||||||||||
], | ||||||||||||||||||
]; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
protected function saveParameters($request, array $annotations) | ||||||||||||||||||
|
@@ -504,7 +516,9 @@ protected function saveGetRequestParameters($rules, array $attributes, array $an | |||||||||||||||||
'in' => 'query', | ||||||||||||||||||
'name' => $parameter, | ||||||||||||||||||
'description' => $description, | ||||||||||||||||||
'type' => $this->getParameterType($validation) | ||||||||||||||||||
'schema' => [ | ||||||||||||||||||
'type' => $this->getParameterType($validation), | ||||||||||||||||||
], | ||||||||||||||||||
]; | ||||||||||||||||||
if (in_array('required', $validation)) { | ||||||||||||||||||
$parameterDefinition['required'] = true; | ||||||||||||||||||
|
@@ -519,14 +533,18 @@ protected function savePostRequestParameters($actionName, $rules, array $attribu | |||||||||||||||||
{ | ||||||||||||||||||
if ($this->requestHasMoreProperties($actionName)) { | ||||||||||||||||||
if ($this->requestHasBody()) { | ||||||||||||||||||
$this->item['parameters'][] = [ | ||||||||||||||||||
'in' => 'body', | ||||||||||||||||||
'name' => 'body', | ||||||||||||||||||
$type = $this->request->header('Content-Type') ?? 'application/json'; | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
$this->item['requestBody'] = [ | ||||||||||||||||||
'content' => [ | ||||||||||||||||||
$type => [ | ||||||||||||||||||
'schema' => [ | ||||||||||||||||||
"\$ref" => "#/components/schemas/{$actionName}Object", | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
], | ||||||||||||||||||
], | ||||||||||||||||||
], | ||||||||||||||||||
'description' => '', | ||||||||||||||||||
'required' => true, | ||||||||||||||||||
'schema' => [ | ||||||||||||||||||
"\$ref" => "#/definitions/{$actionName}Object" | ||||||||||||||||||
] | ||||||||||||||||||
]; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -559,7 +577,7 @@ protected function saveDefinitions($objectName, $rules, $attributes, array $anno | |||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
$data['example'] = $this->generateExample($data['properties']); | ||||||||||||||||||
$this->data['definitions'][$objectName . 'Object'] = $data; | ||||||||||||||||||
$this->data['components']['schemas'][$objectName . 'Object'] = $data; | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
protected function getParameterType(array $validation): string | ||||||||||||||||||
|
@@ -600,8 +618,8 @@ protected function requestHasMoreProperties($actionName): bool | |||||||||||||||||
{ | ||||||||||||||||||
$requestParametersCount = count($this->request->all()); | ||||||||||||||||||
|
||||||||||||||||||
if (isset($this->data['definitions'][$actionName . 'Object']['properties'])) { | ||||||||||||||||||
$objectParametersCount = count($this->data['definitions'][$actionName . 'Object']['properties']); | ||||||||||||||||||
if (isset($this->data['components']['schemas'][$actionName . 'Object']['properties'])) { | ||||||||||||||||||
$objectParametersCount = count($this->data['components']['schemas'][$actionName . 'Object']['properties']); | ||||||||||||||||||
} else { | ||||||||||||||||||
$objectParametersCount = 0; | ||||||||||||||||||
} | ||||||||||||||||||
Comment on lines
+621
to
625
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
@@ -979,11 +997,11 @@ protected function mergeOpenAPIDocs(array &$documentation, array $additionalDocu | |||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
$definitions = array_keys($additionalDocumentation['definitions']); | ||||||||||||||||||
$definitions = array_keys($additionalDocumentation['components']['schemas']); | ||||||||||||||||||
|
||||||||||||||||||
foreach ($definitions as $definition) { | ||||||||||||||||||
if (empty($documentation['definitions'][$definition])) { | ||||||||||||||||||
$documentation['definitions'][$definition] = $additionalDocumentation['definitions'][$definition]; | ||||||||||||||||||
if (empty($documentation['components']['schemas'][$definition])) { | ||||||||||||||||||
$documentation['components']['schemas'][$definition] = $additionalDocumentation['components']['schemas'][$definition]; | ||||||||||||||||||
Comment on lines
+1003
to
+1004
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -48,13 +48,14 @@ class SwaggerSpecValidator | |||||
]; | ||||||
|
||||||
public const REQUIRED_FIELDS = [ | ||||||
'definition' => ['type'], | ||||||
'doc' => ['swagger', 'info', 'paths'], | ||||||
'components' => ['type'], | ||||||
'doc' => ['openapi', 'info', 'paths'], | ||||||
'info' => ['title', 'version'], | ||||||
'item' => ['type'], | ||||||
'header' => ['type'], | ||||||
'operation' => ['responses'], | ||||||
'parameter' => ['in', 'name'], | ||||||
'requestBody' => ['content'], | ||||||
'response' => ['description'], | ||||||
'security_definition' => ['type'], | ||||||
'tag' => ['name'], | ||||||
|
@@ -76,6 +77,7 @@ class SwaggerSpecValidator | |||||
|
||||||
public const MIME_TYPE_MULTIPART_FORM_DATA = 'multipart/form-data'; | ||||||
public const MIME_TYPE_APPLICATION_URLENCODED = 'application/x-www-form-urlencoded'; | ||||||
public const MIME_TYPE_APPLICATION_JSON = 'application/json'; | ||||||
|
||||||
protected $doc; | ||||||
|
||||||
|
@@ -96,9 +98,9 @@ public function validate(array $doc): void | |||||
|
||||||
protected function validateVersion(): void | ||||||
{ | ||||||
$version = Arr::get($this->doc, 'swagger', ''); | ||||||
$version = Arr::get($this->doc, 'openapi', ''); | ||||||
|
||||||
if (version_compare($version, SwaggerService::SWAGGER_VERSION, '!=')) { | ||||||
if (version_compare($version, SwaggerService::OPEN_API_VERSION, '!=')) { | ||||||
throw new InvalidSwaggerVersionException($version); | ||||||
} | ||||||
} | ||||||
|
@@ -128,6 +130,10 @@ protected function validatePaths(): void | |||||
|
||||||
$this->validateParameters($operation, $path, $operationId); | ||||||
|
||||||
if (!empty($operation['requestBody'])) { | ||||||
$this->validateRequestBody($operation, $path, $operationId); | ||||||
} | ||||||
|
||||||
foreach ($operation['responses'] as $statusCode => $response) { | ||||||
$this->validateResponse($response, $statusCode, $operationId); | ||||||
} | ||||||
|
@@ -139,10 +145,10 @@ protected function validatePaths(): void | |||||
|
||||||
protected function validateDefinitions(): void | ||||||
{ | ||||||
$definitions = Arr::get($this->doc, 'definitions', []); | ||||||
$definitions = Arr::get($this->doc, 'components.schemas', []); | ||||||
|
||||||
foreach ($definitions as $index => $definition) { | ||||||
$this->validateFieldsPresent(self::REQUIRED_FIELDS['definition'], "definitions.{$index}"); | ||||||
$this->validateFieldsPresent(self::REQUIRED_FIELDS['components'], "components.schemas.{$index}"); | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -196,10 +202,10 @@ protected function validateResponse(array $response, string $statusCode, string | |||||
array_merge(self::SCHEMA_TYPES, ['file']), | ||||||
"{$responseId}.schema" | ||||||
); | ||||||
} | ||||||
|
||||||
if (!empty($response['items'])) { | ||||||
$this->validateItems($response['items'], "{$responseId}.items"); | ||||||
if (!empty($response['schema']['items'])) { | ||||||
$this->validateItems($response['schema']['items'], "{$responseId}.schema.items"); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -220,8 +226,8 @@ protected function validateParameters(array $operation, string $path, string $op | |||||
|
||||||
$this->validateParameterType($param, $operation, $paramId, $operationId); | ||||||
|
||||||
if (!empty($param['items'])) { | ||||||
$this->validateItems($param['items'], "{$paramId}.items"); | ||||||
if (!empty($param['schema']['items'])) { | ||||||
$this->validateItems($param['schema']['items'], "{$paramId}.schema.items"); | ||||||
} | ||||||
} | ||||||
|
||||||
|
@@ -230,6 +236,38 @@ protected function validateParameters(array $operation, string $path, string $op | |||||
$this->validateBodyParameters($parameters, $operationId); | ||||||
} | ||||||
|
||||||
protected function validateRequestBody(array $operation, string $path, string $operationId): void | ||||||
{ | ||||||
$requestBody = Arr::get($operation, 'requestBody', []); | ||||||
|
||||||
$this->validateFieldsPresent(self::REQUIRED_FIELDS['requestBody'], "{$operationId}.requestBody"); | ||||||
|
||||||
$this->validateRequestBodyContent($requestBody['content'], $operationId); | ||||||
} | ||||||
|
||||||
protected function validateRequestBodyContent(array $content, string $operationId): void | ||||||
{ | ||||||
$allowedContentType = false; | ||||||
|
||||||
$types = [ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's move it to the class const |
||||||
self::MIME_TYPE_APPLICATION_URLENCODED, | ||||||
self::MIME_TYPE_MULTIPART_FORM_DATA, | ||||||
self::MIME_TYPE_APPLICATION_JSON, | ||||||
]; | ||||||
|
||||||
foreach ($types as $type) { | ||||||
if (!empty($content[$type])) { | ||||||
$allowedContentType = true; | ||||||
} | ||||||
} | ||||||
|
||||||
if (!$allowedContentType) { | ||||||
throw new InvalidSwaggerSpecException( | ||||||
"Operation '{$operationId}' has body parameters. Only one or the other is allowed." | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
); | ||||||
} | ||||||
} | ||||||
|
||||||
protected function validateType(array $schema, array $validTypes, string $schemaId): void | ||||||
{ | ||||||
$schemaType = Arr::get($schema, 'type'); | ||||||
|
@@ -313,12 +351,12 @@ protected function validateParameterType(array $param, array $operation, string | |||||
case 'formData': | ||||||
$this->validateFormDataConsumes($operation, $operationId); | ||||||
|
||||||
$requiredFields = ['type']; | ||||||
$requiredFields = ['schema']; | ||||||
$validTypes = array_merge(self::PRIMITIVE_TYPES, ['file']); | ||||||
|
||||||
break; | ||||||
default: | ||||||
$requiredFields = ['type']; | ||||||
$requiredFields = ['schema']; | ||||||
$validTypes = self::PRIMITIVE_TYPES; | ||||||
} | ||||||
|
||||||
|
@@ -393,7 +431,7 @@ protected function validateRefs(): void | |||||
!empty($refFilename) | ||||||
? json_decode(file_get_contents($refFilename), true) | ||||||
: $this->doc, | ||||||
$refParentKey | ||||||
str_replace('/', '.', $refParentKey), | ||||||
); | ||||||
|
||||||
if (!empty($missingRefs)) { | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.