Skip to content

Commit

Permalink
refactor(openapi): parameter's filter description
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Oct 25, 2024
1 parent 34604f4 commit 50ac9eb
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
152 changes: 88 additions & 64 deletions src/OpenApi/Factory/OpenApiFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,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']);

Expand Down Expand Up @@ -654,92 +668,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'], 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() && ($str = $description['description'] ?? '')) {
$parameter = $parameter->withDescription($str);
}

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
Expand Down
3 changes: 2 additions & 1 deletion src/OpenApi/Tests/Factory/OpenApiFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,8 @@ public function testInvoke(): void
'type' => 'string',
'enum' => ['asc', 'desc'],
]),
]
],
deprecated: false
), $filteredPath->getGet());

$paginatedPath = $paths->getPath('/paginated');
Expand Down
17 changes: 15 additions & 2 deletions tests/Fixtures/TestBundle/ApiResource/FilterWithStateOptions.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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;
Expand All @@ -17,7 +28,9 @@
provider: CollectionProvider::class
)]
#[ApiFilter(DateFilter::class, alias: 'filter_with_state_options_date', properties: ['dummyDate' => DateFilter::EXCLUDE_NULL])]
final readonly class FilterWithStateOptions
final class FilterWithStateOptions
{
public function __construct(public string $id, public \DateImmutable $dummyDate, public string $name) {}
public function __construct(public readonly string $id, public readonly \DateTimeImmutable $dummyDate, public readonly string $name)
{
}
}
16 changes: 14 additions & 2 deletions tests/Fixtures/TestBundle/Entity/FilterWithStateOptionsEntity.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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;
Expand All @@ -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,
) {
}
}
3 changes: 2 additions & 1 deletion tests/Functional/Parameters/DoctrineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use ApiPlatform\Tests\Fixtures\TestBundle\Document\SearchFilterParameterDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\FilterWithStateOptionsEntity;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SearchFilterParameter;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\DependencyInjection\ContainerInterface;
Expand Down Expand Up @@ -135,7 +136,7 @@ private function recreateSchema(string $resourceClass): void
$schemaTool = new SchemaTool($manager);
@$schemaTool->dropSchema([$classes]);
@$schemaTool->createSchema([$classes]);
} else {
} elseif ($manager instanceof DocumentManager) {
$schemaManager = $manager->getSchemaManager();
$schemaManager->dropCollections();
}
Expand Down

0 comments on commit 50ac9eb

Please sign in to comment.