diff --git a/CHANGELOG.md b/CHANGELOG.md index d46035549..5ea75a0b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ CHANGELOG ========= +next +----- +* Create top level OpenApi Tag from Tags top level annotations/attributes + 4.26.0 ----- * Add ability to configure UI through configuration diff --git a/src/Describer/OpenApiPhpDescriber.php b/src/Describer/OpenApiPhpDescriber.php index 14b792938..20cc958de 100644 --- a/src/Describer/OpenApiPhpDescriber.php +++ b/src/Describer/OpenApiPhpDescriber.php @@ -135,6 +135,9 @@ public function describe(OA\OpenApi $api): void $annotation->validate(); $mergeProperties->tags[] = $annotation->name; + $tag = Util::getTag($api, $annotation->name); + $tag->mergeProperties($annotation); + continue; } diff --git a/src/OpenApiPhp/Util.php b/src/OpenApiPhp/Util.php index 5dca4db7d..75d50ac2b 100644 --- a/src/OpenApiPhp/Util.php +++ b/src/OpenApiPhp/Util.php @@ -70,6 +70,30 @@ public static function getPath(OA\OpenApi $api, string $path): OA\PathItem return self::getIndexedCollectionItem($api, OA\PathItem::class, $path); } + /** + * Return an existing Tag object from $api->tags[] having its member name set to $name. + * Create, add to $api->tags[] and return this new Tag object and set the property if none found. + * + * @see OA\OpenApi::$tags + * @see OA\Tag::$name + */ + public static function getTag(OA\OpenApi $api, string $name): OA\Tag + { + // Tags ar not considered indexed, so we cannot use getIndexedCollectionItem directly + // because we need to specify that the search should use the "name" property. + $key = self::searchIndexedCollectionItem( + is_array($api->tags) ? $api->tags : [], + 'name', + $name + ); + + if (false === $key) { + $key = self::createCollectionItem($api, 'tags', OA\Tag::class, ['name' => $name]); + } + + return $api->tags[$key]; + } + /** * Return an existing Schema object from $api->components->schemas[] having its member schema set to $schema. * Create, add to $api->components->schemas[] and return this new Schema object and set the property if none found. diff --git a/tests/Functional/Controller/OpenApiTagController.php b/tests/Functional/Controller/OpenApiTagController.php new file mode 100644 index 000000000..735ca3fd3 --- /dev/null +++ b/tests/Functional/Controller/OpenApiTagController.php @@ -0,0 +1,33 @@ + [ + [ + 'name' => 'OpenApiTagController', + 'type' => $type, + ], + ]; + if (property_exists(MapRequestPayload::class, 'type')) { yield 'Symfony 7.1 MapRequestPayload array type' => [ [ diff --git a/tests/Functional/Fixtures/OpenApiTagController.json b/tests/Functional/Fixtures/OpenApiTagController.json new file mode 100644 index 000000000..ecd5c3f6b --- /dev/null +++ b/tests/Functional/Fixtures/OpenApiTagController.json @@ -0,0 +1,44 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "", + "version": "0.0.0" + }, + "paths": { + "/some_post": { + "post": { + "tags": [ + "My tag name" + ], + "operationId": "post_nelmio_apidoc_tests_functional_openapitag_somepost", + "responses": { + "200": { + "description": "" + } + } + } + }, + "/some_get": { + "get": { + "tags": [ + "My tag name" + ], + "operationId": "get_nelmio_apidoc_tests_functional_openapitag_someget", + "responses": { + "200": { + "description": "" + } + } + } + } + }, + "tags": [ + { + "name": "My tag name", + "description": "My description of the tag", + "externalDocs": { + "url": "https://example.com" + } + } + ] +} \ No newline at end of file diff --git a/tests/SwaggerPhp/UtilTest.php b/tests/SwaggerPhp/UtilTest.php index f45699059..2510b2664 100644 --- a/tests/SwaggerPhp/UtilTest.php +++ b/tests/SwaggerPhp/UtilTest.php @@ -866,6 +866,24 @@ public static function provideMergeData(): \Generator ]; } + public function testGetTag(): void + { + $api = self::createObj(OA\OpenApi::class, ['_context' => new Context()]); + self::assertEquals(Generator::UNDEFINED, $api->tags); + + $tag = Util::getTag($api, 'foo'); + self::assertEquals('foo', $tag->name); + self::assertEquals(Generator::UNDEFINED, $tag->description); + self::assertEquals(Generator::UNDEFINED, $tag->externalDocs); + + self::assertIsArray($api->tags); + + $api->tags[] = self::createObj(OA\Tag::class, ['name' => 'bar', 'description' => 'baz']); + $tag = Util::getTag($api, 'bar'); + self::assertEquals('bar', $tag->name); + self::assertEquals('baz', $tag->description); + } + public function assertIsNested(OA\AbstractAnnotation $parent, OA\AbstractAnnotation $child): void { self::assertTrue($child->_context->is('nested'));