From b2883164d85604017627e5d2b77938a28632abeb Mon Sep 17 00:00:00 2001 From: soyuka Date: Wed, 23 Oct 2024 14:24:12 +0200 Subject: [PATCH] refactor(openapi): parameter's filter description --- ...meterResourceMetadataCollectionFactory.php | 1 - src/OpenApi/Factory/OpenApiFactory.php | 153 ++++++++++-------- .../ApiResource/FilterWithStateOptions.php | 15 +- .../Entity/FilterWithStateOptionsEntity.php | 16 +- 4 files changed, 117 insertions(+), 68 deletions(-) diff --git a/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php index 2d784230053..12a89a50fb9 100644 --- a/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php @@ -16,7 +16,6 @@ use ApiPlatform\Doctrine\Odm\State\Options as DoctrineOdmOptions; use ApiPlatform\Doctrine\Orm\State\Options as DoctrineOrmOptions; use ApiPlatform\Metadata\FilterInterface; -use ApiPlatform\Metadata\HeaderParameterInterface; use ApiPlatform\Metadata\HttpOperation; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Parameter; diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index b05d6a5e67b..cada28fc4a6 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -55,6 +55,7 @@ use ApiPlatform\OpenApi\Options; use ApiPlatform\OpenApi\Serializer\NormalizeOperationNameTrait; use ApiPlatform\State\Pagination\PaginationOptions; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\FilterWithStateOptions; use Psr\Container\ContainerInterface; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Routing\RouteCollection; @@ -331,12 +332,26 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection } } + $entityClass = $this->getFilterClass($operation); $openapiParameters = $openapiOperation->getParameters(); foreach ($operation->getParameters() ?? [] as $key => $p) { if (false === $p->getOpenApi()) { continue; } + if (($f = $p->getFilter()) && \is_string($f) && $this->filterLocator->has($f)) { + $filter = $this->filterLocator->get($f); + foreach ($filter->getDescription($entityClass) as $name => $description) { + if ($prop = $p->getProperty()) { + $name = str_replace($prop, $key, $name); + } + + $openapiParameters[] = $this->getFilterParameter($name, $description, $operation->getShortName(), $f); + } + + continue; + } + $in = $p instanceof HeaderParameterInterface ? 'header' : 'query'; $parameter = new Parameter($key, $in, $p->getDescription() ?? "$resourceShortName $key", $p->getRequired() ?? false, false, false, $p->getSchema() ?? ['type' => 'string']); @@ -654,92 +669,102 @@ private function getLinks(ResourceMetadataCollection $resourceMetadataCollection private function getFiltersParameters(CollectionOperationInterface|HttpOperation $operation): array { $parameters = []; - $resourceFilters = $operation->getFilters(); + $entityClass = $this->getFilterClass($operation); + foreach ($resourceFilters ?? [] as $filterId) { if (!$this->filterLocator->has($filterId)) { continue; } $filter = $this->filterLocator->get($filterId); - $entityClass = $operation->getClass(); - if ($options = $operation->getStateOptions()) { - if ($options instanceof DoctrineOptions && $options->getEntityClass()) { - $entityClass = $options->getEntityClass(); - } - - if ($options instanceof DoctrineODMOptions && $options->getDocumentClass()) { - $entityClass = $options->getDocumentClass(); - } + foreach ($filter->getDescription($entityClass) as $name => $description) { + $parameters[] = $this->getFilterParameter($name, $description, $operation->getShortName(), $filterId); } + } - foreach ($filter->getDescription($entityClass) as $name => $data) { - if (isset($data['swagger'])) { - trigger_deprecation('api-platform/core', '4.0', \sprintf('Using the "swagger" field of the %s::getDescription() (%s) is deprecated.', $filter::class, $operation->getShortName())); - } + return $parameters; + } - if (!isset($data['openapi']) || $data['openapi'] instanceof Parameter) { - $schema = $data['schema'] ?? []; + private function getFilterClass(HttpOperation $operation): string + { + $entityClass = $operation->getClass(); + if ($options = $operation->getStateOptions()) { + if ($options instanceof DoctrineOptions && $options->getEntityClass()) { + return $options->getEntityClass(); + } - if (isset($data['type']) && \in_array($data['type'] ?? null, Type::$builtinTypes, true) && !isset($schema['type'])) { - $schema += $this->jsonSchemaTypeFactory ? $this->jsonSchemaTypeFactory->getType(new Type($data['type'], false, null, $data['is_collection'] ?? false)) : $this->getType(new Type($data['type'], false, null, $data['is_collection'] ?? false)); - } + if ($options instanceof DoctrineODMOptions && $options->getDocumentClass()) { + return $options->getDocumentClass(); + } + } - if (!isset($schema['type'])) { - $schema['type'] = 'string'; - } + return $entityClass; + } - $style = 'array' === ($schema['type'] ?? null) && \in_array( - $data['type'], - [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_OBJECT], - true - ) ? 'deepObject' : 'form'; + private function getFilterParameter(string $name, array $description, string $shortName, string $filter): Parameter + { + if (isset($description['swagger'])) { + trigger_deprecation('api-platform/core', '4.0', \sprintf('Using the "swagger" field of the %s::getDescription() (%s) is deprecated.', $filter, $shortName)); + } - $parameter = isset($data['openapi']) && $data['openapi'] instanceof Parameter ? $data['openapi'] : new Parameter(in: 'query', name: $name, style: $style, explode: $data['is_collection'] ?? false); + if (!isset($description['openapi']) || $description['openapi'] instanceof Parameter) { + $schema = $description['schema'] ?? []; - if ('' === $parameter->getDescription() && ($description = $data['description'] ?? '')) { - $parameter = $parameter->withDescription($description); - } + if (isset($description['type']) && \in_array($description['type'] ?? null, Type::$builtinTypes, true) && !isset($schema['type'])) { + $schema += $this->jsonSchemaTypeFactory ? $this->jsonSchemaTypeFactory->getType(new Type($description['type'], false, null, $description['is_collection'] ?? false)) : $this->getType(new Type($description['type'], false, null, $description['is_collection'] ?? false)); + } - if (false === $parameter->getRequired() && false !== ($required = $data['required'] ?? false)) { - $parameter = $parameter->withRequired($required); - } + if (!isset($schema['type'])) { + $schema['type'] = 'string'; + } - $parameters[] = $parameter->withSchema($schema); - continue; - } + $style = 'array' === ($schema['type'] ?? null) && \in_array( + $description['type'], + [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_OBJECT], + true + ) ? 'deepObject' : 'form'; - trigger_deprecation('api-platform/core', '4.0', \sprintf('Not using "%s" on the "openapi" field of the %s::getDescription() (%s) is deprecated.', Parameter::class, $filter::class, $operation->getShortName())); - if ($this->jsonSchemaTypeFactory) { - $schema = $data['schema'] ?? (\in_array($data['type'], Type::$builtinTypes, true) ? $this->jsonSchemaTypeFactory->getType(new Type($data['type'], false, null, $data['is_collection'] ?? false), 'openapi') : ['type' => 'string']); - } else { - $schema = $data['schema'] ?? (\in_array($data['type'], Type::$builtinTypes, true) ? $this->getType(new Type($data['type'], false, null, $data['is_collection'] ?? false)) : ['type' => 'string']); - } + $parameter = isset($description['openapi']) && $description['openapi'] instanceof Parameter ? $description['openapi'] : new Parameter(in: 'query', name: $name, style: $style, explode: $description['is_collection'] ?? false); - $parameters[] = new Parameter( - $name, - 'query', - $data['description'] ?? '', - $data['required'] ?? false, - $data['openapi']['deprecated'] ?? false, - $data['openapi']['allowEmptyValue'] ?? true, - $schema, - 'array' === $schema['type'] && \in_array( - $data['type'], - [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_OBJECT], - true - ) ? 'deepObject' : 'form', - $data['openapi']['explode'] ?? ('array' === $schema['type']), - $data['openapi']['allowReserved'] ?? false, - $data['openapi']['example'] ?? null, - isset( - $data['openapi']['examples'] - ) ? new \ArrayObject($data['openapi']['examples']) : null - ); + if ('' === $parameter->getDescription() && ($description = $description['description'] ?? '')) { + $parameter = $parameter->withDescription($description); + } + + if (false === $parameter->getRequired() && false !== ($required = $description['required'] ?? false)) { + $parameter = $parameter->withRequired($required); } + + return $parameter->withSchema($schema); } - return $parameters; + trigger_deprecation('api-platform/core', '4.0', \sprintf('Not using "%s" on the "openapi" field of the %s::getDescription() (%s) is deprecated.', Parameter::class, $filter, $shortName)); + if ($this->jsonSchemaTypeFactory) { + $schema = $description['schema'] ?? (\in_array($description['type'], Type::$builtinTypes, true) ? $this->jsonSchemaTypeFactory->getType(new Type($description['type'], false, null, $description['is_collection'] ?? false), 'openapi') : ['type' => 'string']); + } else { + $schema = $description['schema'] ?? (\in_array($description['type'], Type::$builtinTypes, true) ? $this->getType(new Type($description['type'], false, null, $description['is_collection'] ?? false)) : ['type' => 'string']); + } + + return new Parameter( + $name, + 'query', + $description['description'] ?? '', + $description['required'] ?? false, + $description['openapi']['deprecated'] ?? false, + $description['openapi']['allowEmptyValue'] ?? true, + $schema, + 'array' === $schema['type'] && \in_array( + $description['type'], + [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_OBJECT], + true + ) ? 'deepObject' : 'form', + $description['openapi']['explode'] ?? ('array' === $schema['type']), + $description['openapi']['allowReserved'] ?? false, + $description['openapi']['example'] ?? null, + isset( + $description['openapi']['examples'] + ) ? new \ArrayObject($description['openapi']['examples']) : null + ); } private function getPaginationParameters(CollectionOperationInterface|HttpOperation $operation): array diff --git a/tests/Fixtures/TestBundle/ApiResource/FilterWithStateOptions.php b/tests/Fixtures/TestBundle/ApiResource/FilterWithStateOptions.php index eb9d491995c..ea032a23fba 100644 --- a/tests/Fixtures/TestBundle/ApiResource/FilterWithStateOptions.php +++ b/tests/Fixtures/TestBundle/ApiResource/FilterWithStateOptions.php @@ -1,5 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; @@ -19,5 +30,7 @@ #[ApiFilter(DateFilter::class, alias: 'filter_with_state_options_date', properties: ['dummyDate' => DateFilter::EXCLUDE_NULL])] final readonly class FilterWithStateOptions { - public function __construct(public string $id, public \DateImmutable $dummyDate, public string $name) {} + public function __construct(public string $id, public \DateTimeImmutable $dummyDate, public string $name) + { + } } diff --git a/tests/Fixtures/TestBundle/Entity/FilterWithStateOptionsEntity.php b/tests/Fixtures/TestBundle/Entity/FilterWithStateOptionsEntity.php index 3858892d7aa..45f05f491bd 100644 --- a/tests/Fixtures/TestBundle/Entity/FilterWithStateOptionsEntity.php +++ b/tests/Fixtures/TestBundle/Entity/FilterWithStateOptionsEntity.php @@ -1,5 +1,16 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity; use Doctrine\ORM\Mapping as ORM; @@ -15,6 +26,7 @@ public function __construct( #[ORM\Column(type: 'date_immutable', nullable: true)] public ?\DateTimeImmutable $dummyDate = null, #[ORM\Column(type: 'string', nullable: true)] - public ?string $name = null - ) {} + public ?string $name = null, + ) { + } }