Skip to content

Commit

Permalink
feat(openapi): add ApiResource::openapi and deprecate openapiContext (#…
Browse files Browse the repository at this point in the history
…5254)

* feat: add ApiResource::openapi and deprecate openapiContext

* fix: review

* fix: deprecations on phpunit tests

* fix: deprecations

* chore: revamp OpenApiFactory

* fix: cs
  • Loading branch information
vincentchalamon authored Dec 16, 2022
1 parent ac71153 commit c145ec7
Show file tree
Hide file tree
Showing 24 changed files with 712 additions and 113 deletions.
28 changes: 27 additions & 1 deletion src/Metadata/ApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Metadata;

use ApiPlatform\Metadata\GraphQl\Operation as GraphQlOperation;
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;

/**
* Resource metadata attribute.
Expand Down Expand Up @@ -52,6 +53,7 @@ class ApiResource
* @param array|null $denormalizationContext https://api-platform.com/docs/core/serialization/#using-serialization-groups
* @param string[]|null $hydraContext https://api-platform.com/docs/core/extending-jsonld-context/#hydra
* @param array|null $openapiContext https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts
* @param bool|OpenApiOperation|null $openapi https://api-platform.com/docs/core/openapi/#using-the-openapi-and-swagger-contexts
* @param array|null $validationContext https://api-platform.com/docs/core/validation/#using-validation-groups
* @param string[] $filters https://api-platform.com/docs/core/filters/#doctrine-orm-and-mongodb-odm-filters
* @param bool|null $elasticsearch https://api-platform.com/docs/core/elasticsearch/
Expand Down Expand Up @@ -110,7 +112,8 @@ public function __construct(
protected ?array $normalizationContext = null,
protected ?array $denormalizationContext = null,
protected ?array $hydraContext = null,
protected ?array $openapiContext = null,
protected ?array $openapiContext = null, // TODO Remove in 4.0
protected bool|OpenApiOperation|null $openapi = null,
protected ?array $validationContext = null,
protected ?array $filters = null,
protected ?bool $elasticsearch = null,
Expand Down Expand Up @@ -548,11 +551,21 @@ public function withHydraContext(array $hydraContext): self
return $self;
}

/**
* TODO Remove in 4.0.
*
* @deprecated
*/
public function getOpenapiContext(): ?array
{
return $this->openapiContext;
}

/**
* TODO Remove in 4.0.
*
* @deprecated
*/
public function withOpenapiContext(array $openapiContext): self
{
$self = clone $this;
Expand All @@ -561,6 +574,19 @@ public function withOpenapiContext(array $openapiContext): self
return $self;
}

public function getOpenapi(): bool|OpenApiOperation|null
{
return $this->openapi;
}

public function withOpenapi(bool|OpenApiOperation $openapi): self
{
$self = clone $this;
$self->openapi = $openapi;

return $self;
}

public function getValidationContext(): ?array
{
return $this->validationContext;
Expand Down
4 changes: 3 additions & 1 deletion src/Metadata/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace ApiPlatform\Metadata;

use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;

#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
final class Delete extends HttpOperation implements DeleteOperationInterface
{
Expand Down Expand Up @@ -43,7 +45,7 @@ public function __construct(

?array $hydraContext = null,
?array $openapiContext = null,
?bool $openapi = null,
bool|OpenApiOperation|null $openapi = null,
?array $exceptionToStatus = null,

?bool $queryParameterValidationEnabled = null,
Expand Down
93 changes: 91 additions & 2 deletions src/Metadata/Extractor/XmlResourceExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Subscription;
use ApiPlatform\Metadata\Post;
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
use ApiPlatform\OpenApi\Model\Parameter;
use ApiPlatform\OpenApi\Model\RequestBody;
use Symfony\Component\Config\Util\XmlUtils;

/**
Expand Down Expand Up @@ -87,7 +91,8 @@ private function buildExtendedBase(\SimpleXMLElement $resource): array
'schemes' => $this->buildArrayValue($resource, 'scheme'),
'cacheHeaders' => $this->buildCacheHeaders($resource),
'hydraContext' => isset($resource->hydraContext->values) ? $this->buildValues($resource->hydraContext->values) : null,
'openapiContext' => isset($resource->openapiContext->values) ? $this->buildValues($resource->openapiContext->values) : null,
'openapiContext' => isset($resource->openapiContext->values) ? $this->buildValues($resource->openapiContext->values) : null, // TODO Remove in 4.0
'openapi' => $this->buildOpenapi($resource),
'paginationViaCursor' => $this->buildPaginationViaCursor($resource),
'exceptionToStatus' => $this->buildExceptionToStatus($resource),
'queryParameterValidationEnabled' => $this->phpize($resource, 'queryParameterValidationEnabled', 'bool'),
Expand Down Expand Up @@ -156,6 +161,91 @@ private function buildFormats(\SimpleXMLElement $resource, string $key): ?array
return $data;
}

private function buildOpenapi(\SimpleXMLElement $resource): bool|OpenApiOperation|null
{
if (!isset($resource->openapi) && !isset($resource['openapi'])) {
return null;
}

if (isset($resource['openapi']) && (\is_bool($resource['openapi']) || \in_array((string) $resource['openapi'], ['1', '0', 'true', 'false'], true))) {
return $this->phpize($resource, 'openapi', 'bool');
}

$openapi = $resource->openapi;
$data = [];
$attributes = $openapi->attributes();
foreach ($attributes as $attribute) {
$data[$attribute->getName()] = $this->phpize($attributes, 'deprecated', 'deprecated' === $attribute->getName() ? 'bool' : 'string');
}

$data['tags'] = $this->buildArrayValue($resource, 'tag');

if (isset($openapi->responses->response)) {
foreach ($openapi->responses->response as $response) {
$data['responses'][(string) $response->attributes()->status] = [
'description' => $this->phpize($response, 'description', 'string'),
'content' => isset($response->content->values) ? $this->buildValues($response->content->values) : null,
'headers' => isset($response->headers->values) ? $this->buildValues($response->headers->values) : null,
'links' => isset($response->links->values) ? $this->buildValues($response->links->values) : null,
];
}
}

$data['externalDocs'] = isset($openapi->externalDocs) ? new ExternalDocumentation(
description: $this->phpize($resource, 'description', 'string'),
url: $this->phpize($resource, 'url', 'string'),
) : null;

if (isset($openapi->parameters->parameter)) {
foreach ($openapi->parameters->parameter as $parameter) {
$data['parameters'][(string) $parameter->attributes()->name] = new Parameter(
name: $this->phpize($parameter, 'name', 'string'),
in: $this->phpize($parameter, 'in', 'string'),
description: $this->phpize($parameter, 'description', 'string'),
required: $this->phpize($parameter, 'required', 'bool'),
deprecated: $this->phpize($parameter, 'deprecated', 'bool'),
allowEmptyValue: $this->phpize($parameter, 'allowEmptyValue', 'bool'),
schema: isset($parameter->schema->values) ? $this->buildValues($parameter->schema->values) : null,
style: $this->phpize($parameter, 'style', 'string'),
explode: $this->phpize($parameter, 'explode', 'bool'),
allowReserved: $this->phpize($parameter, 'allowReserved', 'bool'),
example: $this->phpize($parameter, 'example', 'string'),
examples: isset($parameter->examples->values) ? new \ArrayObject($this->buildValues($parameter->examples->values)) : null,
content: isset($parameter->content->values) ? new \ArrayObject($this->buildValues($parameter->content->values)) : null,
);
}
}
$data['requestBody'] = isset($openapi->requestBody) ? new RequestBody(
description: $this->phpize($openapi->requestBody, 'description', 'string'),
content: isset($openapi->requestBody->content->values) ? new \ArrayObject($this->buildValues($openapi->requestBody->values)) : null,
required: $this->phpize($openapi->requestBody, 'required', 'bool'),
) : null;

$data['callbacks'] = isset($openapi->callbacks->values) ? new \ArrayObject($this->buildValues($openapi->callbacks->values)) : null;

$data['security'] = isset($openapi->security->values) ? $this->buildValues($openapi->security->values) : null;

if (isset($openapi->servers->server)) {
foreach ($openapi->servers->server as $server) {
$data['servers'][] = [
'description' => $this->phpize($server, 'description', 'string'),
'url' => $this->phpize($server, 'url', 'string'),
'variables' => isset($server->variables->values) ? $this->buildValues($server->variables->values) : null,
];
}
}

$data['extensionProperties'] = isset($openapi->extensionProperties->values) ? $this->buildValues($openapi->extensionProperties->values) : null;

foreach ($data as $key => $value) {
if (null === $value) {
unset($data[$key]);
}
}

return new OpenApiOperation(...$data);
}

private function buildUriVariables(\SimpleXMLElement $resource): ?array
{
if (!isset($resource->uriVariables->uriVariable)) {
Expand Down Expand Up @@ -300,7 +390,6 @@ private function buildOperations(\SimpleXMLElement $resource, array $root): ?arr
}

$data[] = array_merge($datum, [
'openapi' => $this->phpize($operation, 'openapi', 'bool'),
'collection' => $this->phpize($operation, 'collection', 'bool'),
'class' => (string) $operation['class'],
'method' => $this->phpize($operation, 'method', 'string'),
Expand Down
37 changes: 35 additions & 2 deletions src/Metadata/Extractor/YamlResourceExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Subscription;
use ApiPlatform\Metadata\Post;
use ApiPlatform\OpenApi\Model\ExternalDocumentation;
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
use ApiPlatform\OpenApi\Model\RequestBody;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;

Expand Down Expand Up @@ -106,7 +109,8 @@ private function buildExtendedBase(array $resource): array
'types' => $this->buildArrayValue($resource, 'types'),
'cacheHeaders' => $this->buildArrayValue($resource, 'cacheHeaders'),
'hydraContext' => $this->buildArrayValue($resource, 'hydraContext'),
'openapiContext' => $this->buildArrayValue($resource, 'openapiContext'),
'openapiContext' => $this->buildArrayValue($resource, 'openapiContext'), // TODO Remove in 4.0
'openapi' => $this->buildOpenapi($resource),
'paginationViaCursor' => $this->buildArrayValue($resource, 'paginationViaCursor'),
'exceptionToStatus' => $this->buildArrayValue($resource, 'exceptionToStatus'),
'defaults' => $this->buildArrayValue($resource, 'defaults'),
Expand Down Expand Up @@ -205,6 +209,36 @@ private function buildUriVariables(array $resource): ?array
return $uriVariables;
}

private function buildOpenapi(array $resource): bool|OpenApiOperation|null
{
if (!\array_key_exists('openapi', $resource)) {
return null;
}

if (!\is_array($resource['openapi'])) {
return $this->phpize($resource, 'openapi', 'bool');
}

$allowedProperties = array_map(fn (\ReflectionProperty $reflProperty): string => $reflProperty->getName(), (new \ReflectionClass(OpenApiOperation::class))->getProperties());
foreach ($resource['openapi'] as $key => $value) {
$resource['openapi'][$key] = match ($key) {
'externalDocs' => new ExternalDocumentation(description: $value['description'] ?? '', url: $value['url'] ?? ''),
'requestBody' => new RequestBody(description: $value['description'] ?? '', content: isset($value['content']) ? new \ArrayObject($value['content'] ?? []) : null, required: $value['required'] ?? false),
'callbacks' => new \ArrayObject($value ?? []),
default => $value,
};

if (\in_array($key, $allowedProperties, true)) {
continue;
}

$resource['openapi']['extensionProperties'][$key] = $value;
unset($resource['openapi'][$key]);
}

return new OpenApiOperation(...$resource['openapi']);
}

/**
* @return bool|string|string[]|null
*/
Expand Down Expand Up @@ -271,7 +305,6 @@ private function buildOperations(array $resource, array $root): ?array
$data[] = array_merge($datum, [
'read' => $this->phpize($operation, 'read', 'bool'),
'deserialize' => $this->phpize($operation, 'deserialize', 'bool'),
'openapi' => $this->phpize($operation, 'openapi', 'bool'),
'validate' => $this->phpize($operation, 'validate', 'bool'),
'write' => $this->phpize($operation, 'write', 'bool'),
'serialize' => $this->phpize($operation, 'serialize', 'bool'),
Expand Down
Loading

0 comments on commit c145ec7

Please sign in to comment.