diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c952f11eb66..f77cf5d9e4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -470,26 +470,6 @@ jobs: export PATH="$PATH:$HOME/.composer/vendor/bin" php-coveralls --coverage_clover=build/logs/behat/clover.xml continue-on-error: true - - name: Export OpenAPI documents - run: | - mkdir -p build/out/openapi - tests/Fixtures/app/console api:openapi:export -o build/out/openapi/openapi_v3.json - tests/Fixtures/app/console api:openapi:export --yaml -o build/out/openapi/openapi_v3.yaml - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version: '14' - - name: Validate OpenAPI documents - run: | - npx swagger-cli validate build/out/openapi/openapi_v3.json - npx swagger-cli validate build/out/openapi/openapi_v3.yaml - - name: Upload OpenAPI artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: openapi-docs-php${{ matrix.php }} - path: build/out/openapi - continue-on-error: true postgresql: name: Behat (PHP ${{ matrix.php }}) (PostgreSQL) @@ -1272,26 +1252,53 @@ jobs: name: behat-logs-php${{ matrix.php }} path: build/logs/behat continue-on-error: true + + openapi: + name: OpenAPI + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + php: + - '8.4' + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: pecl, composer + extensions: intl, bcmath, curl, openssl, mbstring, pdo_sqlite, mongodb + ini-values: memory_limit=-1 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Get composer cache directory + id: composercache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + - name: Update project dependencies + run: | + composer global require soyuka/pmu + composer global config allow-plugins.soyuka/pmu true --no-interaction + composer global link . + - name: Clear test app cache + run: tests/Fixtures/app/console cache:clear --ansi - name: Export OpenAPI documents run: | mkdir -p build/out/openapi - tests/Fixtures/app/console api:openapi:export -o build/out/openapi/openapi_v3.json tests/Fixtures/app/console api:openapi:export --yaml -o build/out/openapi/openapi_v3.yaml - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version: '14' - name: Validate OpenAPI documents run: | - npx swagger-cli validate build/out/openapi/openapi_v3.json - npx swagger-cli validate build/out/openapi/openapi_v3.yaml - - name: Upload OpenAPI artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: openapi-docs-php${{ matrix.php }} - path: build/out/openapi - continue-on-error: true + npx @quobix/vacuum lint -r tests/Fixtures/app/ruleset.yaml build/out/openapi/openapi_v3.yaml -d laravel: name: Laravel (PHP ${{ matrix.php }}) diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index 047d55de361..a6636add814 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -279,7 +279,15 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection continue; } - $parameter = new Parameter($parameterName, 'path', $uriVariable->getDescription() ?? "$resourceShortName identifier", $uriVariable->getRequired() ?? true, false, false, $uriVariable->getSchema() ?? ['type' => 'string']); + $parameter = new Parameter( + $parameterName, + 'path', + $uriVariable->getDescription() ?? "$resourceShortName identifier", + $uriVariable->getRequired() ?? true, + false, + null, + $uriVariable->getSchema() ?? ['type' => 'string'], + ); if ($linkParameter = $uriVariable->getOpenApi()) { $parameter = $this->mergeParameter($parameter, $linkParameter); @@ -329,7 +337,15 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection } $in = $p instanceof HeaderParameterInterface ? 'header' : 'query'; - $defaultParameter = new Parameter($key, $in, $p->getDescription() ?? "$resourceShortName $key", $p->getRequired() ?? false, false, false, $p->getSchema() ?? ['type' => 'string']); + $defaultParameter = new Parameter( + $key, + $in, + $p->getDescription() ?? "$resourceShortName $key", + $p->getRequired() ?? false, + false, + null, + $p->getSchema() ?? ['type' => 'string'], + ); $linkParameter = $p->getOpenApi(); if (null === $linkParameter) { @@ -752,7 +768,7 @@ private function getFilterParameter(string $name, array $description, string $sh $description['description'] ?? '', $description['required'] ?? false, $description['openapi']['deprecated'] ?? false, - $description['openapi']['allowEmptyValue'] ?? true, + $description['openapi']['allowEmptyValue'] ?? null, $schema, 'array' === $schema['type'] && \in_array( $description['type'], @@ -760,7 +776,7 @@ private function getFilterParameter(string $name, array $description, string $sh true ) ? 'deepObject' : 'form', $description['openapi']['explode'] ?? ('array' === $schema['type']), - $description['openapi']['allowReserved'] ?? false, + $description['openapi']['allowReserved'] ?? null, $description['openapi']['example'] ?? null, isset( $description['openapi']['examples'] @@ -777,7 +793,15 @@ private function getPaginationParameters(CollectionOperationInterface|HttpOperat $parameters = []; if ($operation->getPaginationEnabled() ?? $this->paginationOptions->isPaginationEnabled()) { - $parameters[] = new Parameter($this->paginationOptions->getPaginationPageParameterName(), 'query', 'The collection page number', false, false, true, ['type' => 'integer', 'default' => 1]); + $parameters[] = new Parameter( + $this->paginationOptions->getPaginationPageParameterName(), + 'query', + 'The collection page number', + false, + false, + null, + ['type' => 'integer', 'default' => 1], + ); if ($operation->getPaginationClientItemsPerPage() ?? $this->paginationOptions->getClientItemsPerPage()) { $schema = [ @@ -790,12 +814,28 @@ private function getPaginationParameters(CollectionOperationInterface|HttpOperat $schema['maximum'] = $maxItemsPerPage; } - $parameters[] = new Parameter($this->paginationOptions->getItemsPerPageParameterName(), 'query', 'The number of items per page', false, false, true, $schema); + $parameters[] = new Parameter( + $this->paginationOptions->getItemsPerPageParameterName(), + 'query', + 'The number of items per page', + false, + false, + null, + $schema, + ); } } if ($operation->getPaginationClientEnabled() ?? $this->paginationOptions->isPaginationClientEnabled()) { - $parameters[] = new Parameter($this->paginationOptions->getPaginationClientEnabledParameterName(), 'query', 'Enable or disable pagination', false, false, true, ['type' => 'boolean']); + $parameters[] = new Parameter( + $this->paginationOptions->getPaginationClientEnabledParameterName(), + 'query', + 'Enable or disable pagination', + false, + false, + null, + ['type' => 'boolean'], + ); } return $parameters; diff --git a/src/OpenApi/Model/Parameter.php b/src/OpenApi/Model/Parameter.php index 918da615149..d242fa8a6d9 100644 --- a/src/OpenApi/Model/Parameter.php +++ b/src/OpenApi/Model/Parameter.php @@ -17,7 +17,7 @@ final class Parameter { use ExtensionTrait; - public function __construct(private string $name, private string $in, private string $description = '', private bool $required = false, private bool $deprecated = false, private bool $allowEmptyValue = false, private array $schema = [], private ?string $style = null, private bool $explode = false, private bool $allowReserved = false, private $example = null, private ?\ArrayObject $examples = null, private ?\ArrayObject $content = null) + public function __construct(private string $name, private string $in, private string $description = '', private bool $required = false, private bool $deprecated = false, private ?bool $allowEmptyValue = null, private array $schema = [], private ?string $style = null, private bool $explode = false, private ?bool $allowReserved = null, private $example = null, private ?\ArrayObject $examples = null, private ?\ArrayObject $content = null) { if (null === $style) { if ('query' === $in || 'cookie' === $in) { @@ -53,12 +53,12 @@ public function getDeprecated(): bool return $this->deprecated; } - public function canAllowEmptyValue(): bool + public function canAllowEmptyValue(): ?bool { return $this->allowEmptyValue; } - public function getAllowEmptyValue(): bool + public function getAllowEmptyValue(): ?bool { return $this->allowEmptyValue; } @@ -83,12 +83,12 @@ public function getExplode(): bool return $this->explode; } - public function canAllowReserved(): bool + public function canAllowReserved(): ?bool { return $this->allowReserved; } - public function getAllowReserved(): bool + public function getAllowReserved(): ?bool { return $this->allowReserved; } diff --git a/src/OpenApi/Tests/Factory/OpenApiFactoryTest.php b/src/OpenApi/Tests/Factory/OpenApiFactoryTest.php index 17188ab9c06..c784e1570db 100644 --- a/src/OpenApi/Tests/Factory/OpenApiFactoryTest.php +++ b/src/OpenApi/Tests/Factory/OpenApiFactoryTest.php @@ -170,7 +170,6 @@ public function testInvoke(): void in: 'query', description: 'Test modified collection page number', required: false, - allowEmptyValue: true, schema: ['type' => 'integer', 'default' => 1], ), ], @@ -268,7 +267,7 @@ public function testInvoke(): void $baseOperation = (new HttpOperation())->withTypes(['http://schema.example.com/Dummy'])->withInputFormats(self::OPERATION_FORMATS['input_formats'])->withOutputFormats(self::OPERATION_FORMATS['output_formats'])->withClass(Dummy::class)->withShortName('Parameter')->withDescription('This is a dummy'); $parameterResource = (new ApiResource())->withOperations(new Operations([ - 'uriVariableSchema' => (new Get(uriTemplate: '/uri_variable_uuid', uriVariables: ['id' => new Link(schema: ['type' => 'string', 'format' => 'uuid'], description: 'hello', required: true, openApi: new Parameter('id', 'path', allowEmptyValue: true))]))->withOperation($baseOperation), + 'uriVariableSchema' => (new Get(uriTemplate: '/uri_variable_uuid', uriVariables: ['id' => new Link(schema: ['type' => 'string', 'format' => 'uuid'], description: 'hello', required: true, openApi: new Parameter('id', 'path'))]))->withOperation($baseOperation), 'parameters' => (new Put(uriTemplate: '/parameters', parameters: [ 'foo' => new HeaderParameter(description: 'hi', schema: ['type' => 'string', 'format' => 'uuid']), ]))->withOperation($baseOperation), @@ -477,7 +476,7 @@ public function testInvoke(): void 'type' => 'string', 'required' => true, 'strategy' => 'exact', - 'openapi' => new Parameter(in: 'query', name: 'name', example: 'bar', deprecated: true, allowEmptyValue: true, allowReserved: true, explode: true), + 'openapi' => new Parameter(in: 'query', name: 'name', example: 'bar', deprecated: true, allowReserved: true, explode: true), ]]), 'f2' => new DummyFilter(['ha' => [ 'property' => 'foo', @@ -690,16 +689,16 @@ public function testInvoke(): void 'Retrieves the collection of Dummy resources.', null, [ - new Parameter('page', 'query', 'Test modified collection page number', false, false, true, [ + new Parameter('page', 'query', 'Test modified collection page number', false, false, null, [ 'type' => 'integer', 'default' => 1, ]), - new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, true, [ + new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, null, [ 'type' => 'integer', 'default' => 30, 'minimum' => 0, ]), - new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, true, [ + new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, null, [ 'type' => 'boolean', ]), ] @@ -935,29 +934,29 @@ public function testInvoke(): void 'Retrieves the collection of Dummy resources.', null, [ - new Parameter('page', 'query', 'The collection page number', false, false, true, [ + new Parameter('page', 'query', 'The collection page number', false, false, null, [ 'type' => 'integer', 'default' => 1, ]), - new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, true, [ + new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, null, [ 'type' => 'integer', 'default' => 30, 'minimum' => 0, ]), - new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, true, [ + new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, null, [ 'type' => 'boolean', ]), - new Parameter('name', 'query', '', true, true, true, [ + new Parameter('name', 'query', '', true, true, null, [ 'type' => 'string', ], 'form', true, true, 'bar'), - new Parameter('ha', 'query', '', false, false, false, [ + new Parameter('ha', 'query', '', false, false, null, [ 'type' => 'integer', ]), - new Parameter('toto', 'query', '', true, false, false, [ + new Parameter('toto', 'query', '', true, false, null, [ 'type' => 'array', 'items' => ['type' => 'string'], ], 'deepObject', true), - new Parameter('order[name]', 'query', '', false, false, false, [ + new Parameter('order[name]', 'query', '', false, false, null, [ 'type' => 'string', 'enum' => ['asc', 'desc'], ]), @@ -981,17 +980,17 @@ public function testInvoke(): void 'Retrieves the collection of Dummy resources.', null, [ - new Parameter('page', 'query', 'The collection page number', false, false, true, [ + new Parameter('page', 'query', 'The collection page number', false, false, null, [ 'type' => 'integer', 'default' => 1, ]), - new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, true, [ + new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, null, [ 'type' => 'integer', 'default' => 20, 'minimum' => 0, 'maximum' => 80, ]), - new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, true, [ + new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, null, [ 'type' => 'boolean', ]), ] @@ -1186,16 +1185,16 @@ public function testInvoke(): void 'Retrieves the collection of Dummy resources.', null, [ - new Parameter('page', 'query', 'The collection page number', false, false, true, [ + new Parameter('page', 'query', 'The collection page number', false, false, null, [ 'type' => 'integer', 'default' => 1, ]), - new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, true, [ + new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, null, [ 'type' => 'integer', 'default' => 30, 'minimum' => 0, ]), - new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, true, [ + new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, null, [ 'type' => 'boolean', ]), ] @@ -1233,7 +1232,6 @@ public function testInvoke(): void ), $emptyRequestBodyPath->getPost()); $parameter = $paths->getPath('/uri_variable_uuid')->getGet()->getParameters()[0]; - $this->assertTrue($parameter->getAllowEmptyValue()); $this->assertEquals(['type' => 'string', 'format' => 'uuid'], $parameter->getSchema()); $parameter = $paths->getPath('/parameters')->getPut()->getParameters()[0]; @@ -1265,16 +1263,16 @@ public function testInvoke(): void 'Retrieves the collection of Dummy resources.', null, [ - new Parameter('page', 'query', 'The collection page number', false, false, true, [ + new Parameter('page', 'query', 'The collection page number', false, false, null, [ 'type' => 'integer', 'default' => 1, ]), - new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, true, [ + new Parameter('itemsPerPage', 'query', 'The number of items per page', false, false, null, [ 'type' => 'integer', 'default' => 30, 'minimum' => 0, ]), - new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, true, [ + new Parameter('pagination', 'query', 'Enable or disable pagination', false, false, null, [ 'type' => 'boolean', ]), ], diff --git a/tests/Fixtures/TestBundle/ApiResource/Crud.php b/tests/Fixtures/TestBundle/ApiResource/Crud.php index fb021ea407f..8d6bf6143c5 100644 --- a/tests/Fixtures/TestBundle/ApiResource/Crud.php +++ b/tests/Fixtures/TestBundle/ApiResource/Crud.php @@ -23,6 +23,7 @@ use ApiPlatform\OpenApi\Model\Operation; #[ApiResource( + description: 'A resource used for OpenAPI tests.', operations: [ new Get(), new GetCollection(openapi: new Operation(extensionProperties: [OpenApiFactory::API_PLATFORM_TAG => ['internal', 'anotherone']])), diff --git a/tests/Fixtures/TestBundle/ApiResource/EntityClassWithDateTime.php b/tests/Fixtures/TestBundle/ApiResource/EntityClassWithDateTime.php index 63bbcddf1ee..5d1d84f5233 100644 --- a/tests/Fixtures/TestBundle/ApiResource/EntityClassWithDateTime.php +++ b/tests/Fixtures/TestBundle/ApiResource/EntityClassWithDateTime.php @@ -25,7 +25,6 @@ ), new GetCollection( uriTemplate: '/EntityClassWithDateTime', - uriVariables: ['id'] ), ], stateOptions: new Options(entityClass: \ApiPlatform\Tests\Fixtures\TestBundle\Entity\EntityClassWithDateTime::class) diff --git a/tests/Fixtures/TestBundle/ApiResource/Headers.php b/tests/Fixtures/TestBundle/ApiResource/Headers.php index f11be6098c9..7647684d37c 100644 --- a/tests/Fixtures/TestBundle/ApiResource/Headers.php +++ b/tests/Fixtures/TestBundle/ApiResource/Headers.php @@ -27,7 +27,7 @@ )] class Headers { - public $id; + public int $id; public static function provide(): self { diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue6211/ArrayPropertyDtoOperation.php b/tests/Fixtures/TestBundle/ApiResource/Issue6211/ArrayPropertyDtoOperation.php index c09c9fa6d1f..17dd526b68f 100644 --- a/tests/Fixtures/TestBundle/ApiResource/Issue6211/ArrayPropertyDtoOperation.php +++ b/tests/Fixtures/TestBundle/ApiResource/Issue6211/ArrayPropertyDtoOperation.php @@ -16,7 +16,7 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Tests\Fixtures\TestBundle\Dto\ArrayPropertyDto; -#[Get(provider: [ArrayPropertyDtoOperation::class, 'provide'], output: ArrayPropertyDto::class)] +#[Get(provider: [ArrayPropertyDtoOperation::class, 'provide'], output: ArrayPropertyDto::class, openapi: false)] class ArrayPropertyDtoOperation { public static function provide(): ArrayPropertyDto diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue6299/Issue6299.php b/tests/Fixtures/TestBundle/ApiResource/Issue6299/Issue6299.php index 35c4f08dbd5..d493c82ff46 100644 --- a/tests/Fixtures/TestBundle/ApiResource/Issue6299/Issue6299.php +++ b/tests/Fixtures/TestBundle/ApiResource/Issue6299/Issue6299.php @@ -17,7 +17,7 @@ use ApiPlatform\Metadata\Get; #[ApiResource] -#[Get(output: Issue6299OutputDto::class)] +#[Get(output: Issue6299OutputDto::class, openapi: false)] final class Issue6299 { } diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue6355/OrderProductCount.php b/tests/Fixtures/TestBundle/ApiResource/Issue6355/OrderProductCount.php index ef83f832561..64ba37c7ae6 100644 --- a/tests/Fixtures/TestBundle/ApiResource/Issue6355/OrderProductCount.php +++ b/tests/Fixtures/TestBundle/ApiResource/Issue6355/OrderProductCount.php @@ -32,6 +32,7 @@ class: OrderDto::class, read: false, write: false, name: 'order_product_update_count', + uriVariables: ['id'] ), ], order: ['position' => 'ASC'], diff --git a/tests/Fixtures/TestBundle/ApiResource/Issue6810/JsonLdContextOutput.php b/tests/Fixtures/TestBundle/ApiResource/Issue6810/JsonLdContextOutput.php index fe1ee0d5548..d09a82bc8da 100644 --- a/tests/Fixtures/TestBundle/ApiResource/Issue6810/JsonLdContextOutput.php +++ b/tests/Fixtures/TestBundle/ApiResource/Issue6810/JsonLdContextOutput.php @@ -16,7 +16,7 @@ use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Operation; -#[Get('/json_ld_context_output', provider: [self::class, 'getData'], output: Output::class, normalizationContext: ['hydra_prefix' => false])] +#[Get('/json_ld_context_output', provider: [self::class, 'getData'], output: Output::class, normalizationContext: ['hydra_prefix' => false], openapi: false)] class JsonLdContextOutput { public function __construct(public string $id) diff --git a/tests/Fixtures/TestBundle/Entity/AttributeOnlyOperation.php b/tests/Fixtures/TestBundle/Entity/AttributeOnlyOperation.php index e13550bb603..7163c33cc36 100644 --- a/tests/Fixtures/TestBundle/Entity/AttributeOnlyOperation.php +++ b/tests/Fixtures/TestBundle/Entity/AttributeOnlyOperation.php @@ -14,8 +14,9 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity; use ApiPlatform\Metadata\Get; +use ApiPlatform\OpenApi\Model\Operation; -#[Get(name: 'my own name')] +#[Get(name: 'my own name', openapi: new Operation(operationId: 'my_own_name'))] final class AttributeOnlyOperation { } diff --git a/tests/Fixtures/TestBundle/Entity/FilteredDateParameter.php b/tests/Fixtures/TestBundle/Entity/FilteredDateParameter.php index 609982b17db..01bb7b3ad7a 100644 --- a/tests/Fixtures/TestBundle/Entity/FilteredDateParameter.php +++ b/tests/Fixtures/TestBundle/Entity/FilteredDateParameter.php @@ -32,18 +32,18 @@ 'date' => new QueryParameter( filter: new DateFilter(), property: 'createdAt', - openApi: new Parameter('createdAt', 'query', allowEmptyValue: true) + openApi: new Parameter('date', 'query', allowEmptyValue: true) ), 'date_include_null_always' => new QueryParameter( filter: new DateFilter(), property: 'createdAt', filterContext: DateFilterInterface::INCLUDE_NULL_BEFORE_AND_AFTER, - openApi: new Parameter('createdAt', 'query', allowEmptyValue: true) + openApi: new Parameter('date_include_null_always', 'query', allowEmptyValue: true) ), 'date_old_way' => new QueryParameter( filter: new DateFilter(properties: ['createdAt' => DateFilterInterface::INCLUDE_NULL_BEFORE_AND_AFTER]), property: 'createdAt', - openApi: new Parameter('createdAt', 'query', allowEmptyValue: true) + openApi: new Parameter('date_old_way', 'query', allowEmptyValue: true) ), ], )] diff --git a/tests/Fixtures/TestBundle/Entity/FilteredRangeParameter.php b/tests/Fixtures/TestBundle/Entity/FilteredRangeParameter.php index 5427d7f9b57..dace7ef7e1e 100644 --- a/tests/Fixtures/TestBundle/Entity/FilteredRangeParameter.php +++ b/tests/Fixtures/TestBundle/Entity/FilteredRangeParameter.php @@ -26,12 +26,12 @@ parameters: [ 'quantity' => new QueryParameter( filter: new RangeFilter(), - openApi: new Parameter('createdAt', 'query', allowEmptyValue: true) + openApi: new Parameter('quantity', 'query', allowEmptyValue: true) ), 'amount' => new QueryParameter( filter: new RangeFilter(), property: 'quantity', - openApi: new Parameter('createdAt', 'query', allowEmptyValue: true) + openApi: new Parameter('amount', 'query', allowEmptyValue: true) ), ], )] diff --git a/tests/Fixtures/TestBundle/Entity/IdentifierShortcut.php b/tests/Fixtures/TestBundle/Entity/IdentifierShortcut.php deleted file mode 100644 index ffa3eadfd7a..00000000000 --- a/tests/Fixtures/TestBundle/Entity/IdentifierShortcut.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * 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 ApiPlatform\Metadata\Patch; - -#[Patch(uriTemplate: '/identifiers_shortcut/{id}', uriVariables: [self::class, 'id'])] -class IdentifierShortcut -{ - public $id; -} diff --git a/tests/Fixtures/TestBundle/Entity/Issue5625/Currency.php b/tests/Fixtures/TestBundle/Entity/Issue5625/Currency.php index 7086fdccff8..b2bef0d1764 100644 --- a/tests/Fixtures/TestBundle/Entity/Issue5625/Currency.php +++ b/tests/Fixtures/TestBundle/Entity/Issue5625/Currency.php @@ -21,7 +21,7 @@ * Currency. */ #[ApiResource(operations: [ - new Get(uriTemplate: '/get_security_1', openapi: new Operation(security: [['JWT' => ['CURRENCY_READ']]])), + new Get(uriTemplate: '/get_security_1', openapi: new Operation(security: [['oauth' => ['CURRENCY_READ']]])), ])] class Currency { diff --git a/tests/Fixtures/TestBundle/Entity/Issue5662/Book.php b/tests/Fixtures/TestBundle/Entity/Issue5662/Book.php index bc930384fb2..960a74460aa 100644 --- a/tests/Fixtures/TestBundle/Entity/Issue5662/Book.php +++ b/tests/Fixtures/TestBundle/Entity/Issue5662/Book.php @@ -13,10 +13,12 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5662; +use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Operation; +#[ApiResource(openapi: false)] #[GetCollection( uriTemplate: '/issue5662/books{._format}', itemUriTemplate: '/issue5662/books/{id}{._format}', diff --git a/tests/Fixtures/TestBundle/Entity/Issue5662/Review.php b/tests/Fixtures/TestBundle/Entity/Issue5662/Review.php index f769515c8ff..74a6a66539a 100644 --- a/tests/Fixtures/TestBundle/Entity/Issue5662/Review.php +++ b/tests/Fixtures/TestBundle/Entity/Issue5662/Review.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue5662; +use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; @@ -20,6 +21,7 @@ use ApiPlatform\Metadata\Post; use ApiPlatform\State\CreateProvider; +#[ApiResource(openapi: false)] #[GetCollection( uriTemplate: '/issue5662/admin/reviews{._format}', itemUriTemplate: '/issue5662/reviews/{id}{._format}', diff --git a/tests/Fixtures/TestBundle/Entity/JsonSchemaResource.php b/tests/Fixtures/TestBundle/Entity/JsonSchemaResource.php index b6eba37f58f..420354d3c1b 100644 --- a/tests/Fixtures/TestBundle/Entity/JsonSchemaResource.php +++ b/tests/Fixtures/TestBundle/Entity/JsonSchemaResource.php @@ -22,7 +22,7 @@ class JsonSchemaResource { #[ApiProperty(identifier: true)] - public $id; + public string $id; #[ApiProperty(writable: false, readableLink: true)] public ?JsonSchemaResourceRelated $resourceRelated = null; diff --git a/tests/Fixtures/TestBundle/Entity/SearchFilterParameter.php b/tests/Fixtures/TestBundle/Entity/SearchFilterParameter.php index 8cd10c57c50..533bb7a14b7 100644 --- a/tests/Fixtures/TestBundle/Entity/SearchFilterParameter.php +++ b/tests/Fixtures/TestBundle/Entity/SearchFilterParameter.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity; use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\GraphQl\QueryCollection; use ApiPlatform\Metadata\QueryParameter; @@ -22,6 +23,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\Filter\SearchTextAndDateFilter; use Doctrine\ORM\Mapping as ORM; +#[ApiResource(openapi: false)] #[GetCollection( uriTemplate: 'search_filter_parameter{._format}', parameters: [ diff --git a/tests/Fixtures/TestBundle/Entity/User.php b/tests/Fixtures/TestBundle/Entity/User.php index d820cadefe7..004f51b5d61 100644 --- a/tests/Fixtures/TestBundle/Entity/User.php +++ b/tests/Fixtures/TestBundle/Entity/User.php @@ -36,12 +36,6 @@ * @author Kévin Dunglas */ #[ApiResource(operations: [ - new Get(), - new Put(), - new Delete(), - new Put(input: RecoverPasswordInput::class, output: RecoverPasswordOutput::class, uriTemplate: 'users/recover/{id}', processor: RecoverPasswordProcessor::class), - new Post(), - new GetCollection(), new Post( uriTemplate: '/users/password_reset_request', messenger: 'input', @@ -50,6 +44,18 @@ normalizationContext: ['groups' => ['user_password_reset_request']], denormalizationContext: ['groups' => ['user_password_reset_request']] ), + new Put( + input: RecoverPasswordInput::class, + output: RecoverPasswordOutput::class, + uriTemplate: 'users/recover/{id}', + processor: RecoverPasswordProcessor::class, + openapi: false, // ambigous path + ), + new Get(), + new Put(), + new Delete(), + new Post(), + new GetCollection(), new Get('users-with-groups/{id}', normalizationContext: ['groups' => ['api-test-case-group']]), new GetCollection('users-with-groups', normalizationContext: ['groups' => ['api-test-case-group']]), ], normalizationContext: ['groups' => ['user', 'user-read']], denormalizationContext: ['groups' => ['user', 'user-write']])] diff --git a/tests/Fixtures/app/ruleset.yaml b/tests/Fixtures/app/ruleset.yaml new file mode 100644 index 00000000000..d0daaa288b8 --- /dev/null +++ b/tests/Fixtures/app/ruleset.yaml @@ -0,0 +1,9 @@ +extends: [[spectral:oas, recommended]] +rules: + circular-references: false + operation-success-response: false + oas3-parameter-description: false + oas3-missing-example: false + description-duplication: false + component-description: false + paths-kebab-case: false diff --git a/tests/Functional/OpenApiTest.php b/tests/Functional/OpenApiTest.php index be6443fec7e..aaf5fcff365 100644 --- a/tests/Functional/OpenApiTest.php +++ b/tests/Functional/OpenApiTest.php @@ -82,7 +82,7 @@ public function testFilterExtensionTags(): void $this->assertArrayHasKey('/cruds', $res['paths']); $this->assertArrayHasKey('post', $res['paths']['/cruds']); $this->assertArrayHasKey('get', $res['paths']['/cruds']); - $this->assertEquals([['name' => 'Crud']], $res['tags']); + $this->assertEquals([['name' => 'Crud', 'description' => 'A resource used for OpenAPI tests.']], $res['tags']); $response = self::createClient()->request('GET', '/docs?filter_tags[]=anotherone', [ 'headers' => ['Accept' => 'application/vnd.openapi+json'], @@ -96,6 +96,6 @@ public function testFilterExtensionTags(): void $this->assertArrayNotHasKey('post', $res['paths']['/cruds']); $this->assertArrayHasKey('get', $res['paths']['/cruds']); $this->assertArrayHasKey('/crud_open_api_api_platform_tags/{id}', $res['paths']); - $this->assertEquals([['name' => 'Crud'], ['name' => 'CrudOpenApiApiPlatformTag', 'description' => 'Something nice']], $res['tags']); + $this->assertEquals([['name' => 'Crud', 'description' => 'A resource used for OpenAPI tests.'], ['name' => 'CrudOpenApiApiPlatformTag', 'description' => 'Something nice']], $res['tags']); } } diff --git a/tests/OpenApi/Command/OpenApiCommandTest.php b/tests/OpenApi/Command/OpenApiCommandTest.php index ac6fdc2898a..faf4ffbf3d8 100644 --- a/tests/OpenApi/Command/OpenApiCommandTest.php +++ b/tests/OpenApi/Command/OpenApiCommandTest.php @@ -67,12 +67,12 @@ public function testExecute(): void #[\PHPUnit\Framework\Attributes\Group('orm')] public function testExecuteWithYaml(): void { - // $this->setMetadataClasses([DummyCar::class, Currency::class]); $this->tester->run(['command' => 'api:openapi:export', '--yaml' => true]); $result = $this->tester->getDisplay(); $this->assertYaml($result); + $operationId = 'api_dummy_cars_get_collection'; $expected = <<assertStringContainsString(str_replace(\PHP_EOL, "\n", $expected), $result, 'nested object should be present.'); + $this->assertStringContainsString($expected, $result, 'nested object should be present.'); $operationId = 'api_dummy_cars_id_get'; $expected = <<assertStringContainsString(str_replace(\PHP_EOL, "\n", $expected), $result, 'arrays should be correctly formatted.'); + $this->assertStringContainsString($expected, $result, 'arrays should be correctly formatted.'); $this->assertStringContainsString('openapi: '.OpenApi::VERSION, $result); $expected = <<assertStringContainsString(str_replace(\PHP_EOL, "\n", $expected), $result, 'multiline formatting must be preserved (using literal style).'); + $this->assertStringContainsString($expected, $result, 'multiline formatting must be preserved (using literal style).'); $expected = <<assertStringContainsString(str_replace(\PHP_EOL, "\n", $expected), $result); + $this->assertStringContainsString($expected, $result); $expected = <<assertStringContainsString(str_replace(\PHP_EOL, "\n", $expected), $result); + $this->assertStringContainsString($expected, $result); } public function testWriteToFile(): void { - // $this->setMetadataClasses([DummyCar::class]); /** @var string $tmpFile */ $tmpFile = tempnam(sys_get_temp_dir(), 'test_write_to_file'); @@ -136,7 +135,6 @@ public function testWriteToFile(): void */ public function testBackedEnumExamplesAreNotLost(): void { - // $this->setMetadataClasses([Issue6317::class]); $this->tester->run(['command' => 'api:openapi:export']); $result = $this->tester->getDisplay(); $json = json_decode($result, true, 512, \JSON_THROW_ON_ERROR); @@ -175,6 +173,6 @@ public function testFilterXApiPlatformTag(): void $this->assertArrayHasKey('/cruds', $res['paths']); $this->assertArrayNotHasKey('post', $res['paths']['/cruds']); $this->assertArrayHasKey('get', $res['paths']['/cruds']); - $this->assertEquals([['name' => 'Crud']], $res['tags']); + $this->assertEquals([['name' => 'Crud', 'description' => 'A resource used for OpenAPI tests.']], $res['tags']); } }