diff --git a/composer.json b/composer.json index 3303bccd7..3ff9ab761 100644 --- a/composer.json +++ b/composer.json @@ -56,6 +56,7 @@ "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0", "symfony/templating": "^5.4 || ^6.4 || ^7.0", "symfony/twig-bundle": "^5.4 || ^6.4 || ^7.0", + "symfony/uid": "^5.4 || ^6.4 || ^7.0", "symfony/validator": "^5.4 || ^6.4 || ^7.0", "willdurand/hateoas-bundle": "^1.0 || ^2.0" }, diff --git a/config/services.xml b/config/services.xml index 75873f9c7..3382906f2 100644 --- a/config/services.xml +++ b/config/services.xml @@ -151,6 +151,10 @@ + + + + diff --git a/src/DependencyInjection/NelmioApiDocExtension.php b/src/DependencyInjection/NelmioApiDocExtension.php index 81f927330..4376aa8a1 100644 --- a/src/DependencyInjection/NelmioApiDocExtension.php +++ b/src/DependencyInjection/NelmioApiDocExtension.php @@ -171,6 +171,10 @@ public function load(array $configs, ContainerBuilder $container): void $container->registerForAutoconfiguration(ModelDescriberInterface::class) ->addTag('nelmio_api_doc.model_describer'); + if (!class_exists(\Symfony\Component\Uid\AbstractUid::class)) { + $container->removeDefinition('nelmio_api_doc.object_model.property_describers.uuid'); + } + // Import services needed for each library $loader->load('php_doc.xml'); diff --git a/src/PropertyDescriber/UuidPropertyDescriber.php b/src/PropertyDescriber/UuidPropertyDescriber.php new file mode 100644 index 000000000..865124f4f --- /dev/null +++ b/src/PropertyDescriber/UuidPropertyDescriber.php @@ -0,0 +1,32 @@ +type = 'string'; + $property->format = 'uuid'; + } + + public function supports(array $types): bool + { + return 1 === count($types) + && Type::BUILTIN_TYPE_OBJECT === $types[0]->getBuiltinType() + && is_a($types[0]->getClassName(), AbstractUid::class, true); + } +} diff --git a/tests/Functional/Controller/ApiController80.php b/tests/Functional/Controller/ApiController80.php index ee4d05ff0..caab30d52 100644 --- a/tests/Functional/Controller/ApiController80.php +++ b/tests/Functional/Controller/ApiController80.php @@ -26,6 +26,7 @@ use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithNullableSchemaSet; use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithObjectType; use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithRef; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithUuid; use Nelmio\ApiDocBundle\Tests\Functional\Entity\RangeInteger; use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints80; use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups; @@ -414,6 +415,17 @@ public function entityWithObjectType() { } + /** + * @Route("/entity-with-uuid", methods={"GET", "POST"}) + * + * @OA\Response(response=200, description="success", @OA\JsonContent( + * ref=@Model(type=EntityWithUuid::class), + * )) + */ + public function entityWithUuid() + { + } + /** * @Route("/form-with-alternate-type", methods={"POST"}) * diff --git a/tests/Functional/Controller/ApiController81.php b/tests/Functional/Controller/ApiController81.php index 3b80bce89..489c92ca4 100644 --- a/tests/Functional/Controller/ApiController81.php +++ b/tests/Functional/Controller/ApiController81.php @@ -27,6 +27,7 @@ use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithNullableSchemaSet; use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithObjectType; use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithRef; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithUuid; use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\ArrayQueryModel; use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\FilterQueryModel; use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\PaginationQueryModel; @@ -345,6 +346,18 @@ public function entityWithObjectType() { } + #[Route('/entity-with-uuid', methods: ['GET', 'POST'])] + #[OA\Response( + response: 200, + description: 'success', + content: new OA\JsonContent( + ref: new Model(type: EntityWithUuid::class), + ), + )] + public function entityWithUuid() + { + } + #[Route('/form-with-alternate-type', methods: ['POST'])] #[OA\Response( response: 204, diff --git a/tests/Functional/Entity/EntityWithUuid.php b/tests/Functional/Entity/EntityWithUuid.php new file mode 100644 index 000000000..6515b95cb --- /dev/null +++ b/tests/Functional/Entity/EntityWithUuid.php @@ -0,0 +1,26 @@ +id = Uuid::v1(); + $this->name = $name; + } +} diff --git a/tests/Functional/FunctionalTest.php b/tests/Functional/FunctionalTest.php index 88ff470ff..6a534e4b1 100644 --- a/tests/Functional/FunctionalTest.php +++ b/tests/Functional/FunctionalTest.php @@ -896,6 +896,24 @@ public function testEntitiesWithOverriddenSchemaTypeDoNotReadOtherProperties(): self::assertSame(Generator::UNDEFINED, $model->properties); } + public function testEntityWithUuid(): void + { + self::assertEquals([ + 'schema' => 'EntityWithUuid', + 'type' => 'object', + 'required' => ['id', 'name'], + 'properties' => [ + 'id' => [ + 'type' => 'string', + 'format' => 'uuid', + ], + 'name' => [ + 'type' => 'string', + ], + ], + ], json_decode($this->getModel('EntityWithUuid')->toJson(), true)); + } + public function testEntitiesWithRefInSchemaDoNoReadOtherProperties(): void { $model = $this->getModel('EntityWithRef'); diff --git a/tests/PropertyDescriber/UuidPropertyDescriberTest.php b/tests/PropertyDescriber/UuidPropertyDescriberTest.php new file mode 100644 index 000000000..6b3d904c3 --- /dev/null +++ b/tests/PropertyDescriber/UuidPropertyDescriberTest.php @@ -0,0 +1,77 @@ +supports([$type])); + } + + public function testSupportsNoIntPropertyType(): void + { + $type = new Type(Type::BUILTIN_TYPE_INT, false); + + $describer = new UuidPropertyDescriber(); + + self::assertFalse($describer->supports([$type])); + } + + public function testSupportsNoDifferentObjectPropertyType(): void + { + $type = new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTimeInterface::class); + + $describer = new UuidPropertyDescriber(); + + self::assertFalse($describer->supports([$type])); + } + + public function testDescribeUuidPropertyType(): void + { + $property = $this->initProperty(); + $schema = $this->initSchema(); + + $describer = new UuidPropertyDescriber(); + $describer->describe([], $property, [], $schema); + + self::assertSame('string', $property->type); + self::assertSame('uuid', $property->format); + } + + private function initProperty(): \OpenApi\Annotations\Property + { + if (PHP_VERSION_ID < 80000) { + return new \OpenApi\Annotations\Property([]); + } + + return new \OpenApi\Attributes\Property(); // union types, used in schema attribute require PHP >= 8.0.0 + } + + private function initSchema(): \OpenApi\Annotations\Schema + { + if (PHP_VERSION_ID < 80000) { + return new \OpenApi\Annotations\Schema([]); + } + + return new \OpenApi\Attributes\Schema(); // union types, used in schema attribute require PHP >= 8.0.0 + } +}