Skip to content

Commit 221a1fe

Browse files
stollrDjordyKoert
andauthored
feat: added symfony/uuid property describer (#2098)
This describer adds support for UUIDs from the following libraries: symfony/uid and ramsey/uuid This makes types of these classes configured as ``` "type": "string", "format": "uuid", "pattern": "^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$" ``` This is based on the [spec](https://swagger.io/docs/specification/data-models/data-types/#string). --------- Co-authored-by: djordy <djordy.koert@yoursurprise.com>
1 parent 599283f commit 221a1fe

File tree

9 files changed

+187
-0
lines changed

9 files changed

+187
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"symfony/stopwatch": "^5.4 || ^6.4 || ^7.0",
5757
"symfony/templating": "^5.4 || ^6.4 || ^7.0",
5858
"symfony/twig-bundle": "^5.4 || ^6.4 || ^7.0",
59+
"symfony/uid": "^5.4 || ^6.4 || ^7.0",
5960
"symfony/validator": "^5.4 || ^6.4 || ^7.0",
6061
"willdurand/hateoas-bundle": "^1.0 || ^2.0"
6162
},

config/services.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@
151151
<tag name="nelmio_api_doc.object_model.property_describer" priority="-1000" />
152152
</service>
153153

154+
<service id="nelmio_api_doc.object_model.property_describers.uuid" class="Nelmio\ApiDocBundle\PropertyDescriber\UuidPropertyDescriber" public="false">
155+
<tag name="nelmio_api_doc.object_model.property_describer" />
156+
</service>
157+
154158
<!-- Form Type Extensions -->
155159

156160
<service id="nelmio_api_doc.form.documentation_extension" class="Nelmio\ApiDocBundle\Form\Extension\DocumentationExtension">

src/DependencyInjection/NelmioApiDocExtension.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ public function load(array $configs, ContainerBuilder $container): void
171171
$container->registerForAutoconfiguration(ModelDescriberInterface::class)
172172
->addTag('nelmio_api_doc.model_describer');
173173

174+
if (!class_exists(\Symfony\Component\Uid\AbstractUid::class)) {
175+
$container->removeDefinition('nelmio_api_doc.object_model.property_describers.uuid');
176+
}
177+
174178
// Import services needed for each library
175179
$loader->load('php_doc.xml');
176180

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the NelmioApiDocBundle package.
5+
*
6+
* (c) Nelmio
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Nelmio\ApiDocBundle\PropertyDescriber;
13+
14+
use OpenApi\Annotations as OA;
15+
use Symfony\Component\PropertyInfo\Type;
16+
use Symfony\Component\Uid\AbstractUid;
17+
18+
final class UuidPropertyDescriber implements PropertyDescriberInterface
19+
{
20+
public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null)
21+
{
22+
$property->type = 'string';
23+
$property->format = 'uuid';
24+
}
25+
26+
public function supports(array $types): bool
27+
{
28+
return 1 === count($types)
29+
&& Type::BUILTIN_TYPE_OBJECT === $types[0]->getBuiltinType()
30+
&& is_a($types[0]->getClassName(), AbstractUid::class, true);
31+
}
32+
}

tests/Functional/Controller/ApiController80.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithNullableSchemaSet;
2727
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithObjectType;
2828
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithRef;
29+
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithUuid;
2930
use Nelmio\ApiDocBundle\Tests\Functional\Entity\RangeInteger;
3031
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints80;
3132
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups;
@@ -414,6 +415,17 @@ public function entityWithObjectType()
414415
{
415416
}
416417

418+
/**
419+
* @Route("/entity-with-uuid", methods={"GET", "POST"})
420+
*
421+
* @OA\Response(response=200, description="success", @OA\JsonContent(
422+
* ref=@Model(type=EntityWithUuid::class),
423+
* ))
424+
*/
425+
public function entityWithUuid()
426+
{
427+
}
428+
417429
/**
418430
* @Route("/form-with-alternate-type", methods={"POST"})
419431
*

tests/Functional/Controller/ApiController81.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithNullableSchemaSet;
2828
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithObjectType;
2929
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithRef;
30+
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithUuid;
3031
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\ArrayQueryModel;
3132
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\FilterQueryModel;
3233
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\PaginationQueryModel;
@@ -345,6 +346,18 @@ public function entityWithObjectType()
345346
{
346347
}
347348

349+
#[Route('/entity-with-uuid', methods: ['GET', 'POST'])]
350+
#[OA\Response(
351+
response: 200,
352+
description: 'success',
353+
content: new OA\JsonContent(
354+
ref: new Model(type: EntityWithUuid::class),
355+
),
356+
)]
357+
public function entityWithUuid()
358+
{
359+
}
360+
348361
#[Route('/form-with-alternate-type', methods: ['POST'])]
349362
#[OA\Response(
350363
response: 204,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the NelmioApiDocBundle package.
5+
*
6+
* (c) Nelmio
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;
13+
14+
use Symfony\Component\Uid\Uuid;
15+
16+
class EntityWithUuid
17+
{
18+
public Uuid $id;
19+
public string $name;
20+
21+
public function __construct(string $name)
22+
{
23+
$this->id = Uuid::v1();
24+
$this->name = $name;
25+
}
26+
}

tests/Functional/FunctionalTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,24 @@ public function testEntitiesWithOverriddenSchemaTypeDoNotReadOtherProperties():
896896
self::assertSame(Generator::UNDEFINED, $model->properties);
897897
}
898898

899+
public function testEntityWithUuid(): void
900+
{
901+
self::assertEquals([
902+
'schema' => 'EntityWithUuid',
903+
'type' => 'object',
904+
'required' => ['id', 'name'],
905+
'properties' => [
906+
'id' => [
907+
'type' => 'string',
908+
'format' => 'uuid',
909+
],
910+
'name' => [
911+
'type' => 'string',
912+
],
913+
],
914+
], json_decode($this->getModel('EntityWithUuid')->toJson(), true));
915+
}
916+
899917
public function testEntitiesWithRefInSchemaDoNoReadOtherProperties(): void
900918
{
901919
$model = $this->getModel('EntityWithRef');
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the NelmioApiDocBundle package.
5+
*
6+
* (c) Nelmio
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Nelmio\ApiDocBundle\Tests\PropertyDescriber;
13+
14+
use Nelmio\ApiDocBundle\PropertyDescriber\UuidPropertyDescriber;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\PropertyInfo\Type;
17+
use Symfony\Component\Uid\Uuid;
18+
19+
class UuidPropertyDescriberTest extends TestCase
20+
{
21+
public function testSupportsUuidPropertyType(): void
22+
{
23+
$type = new Type(Type::BUILTIN_TYPE_OBJECT, false, Uuid::class);
24+
25+
$describer = new UuidPropertyDescriber();
26+
27+
self::assertTrue($describer->supports([$type]));
28+
}
29+
30+
public function testSupportsNoIntPropertyType(): void
31+
{
32+
$type = new Type(Type::BUILTIN_TYPE_INT, false);
33+
34+
$describer = new UuidPropertyDescriber();
35+
36+
self::assertFalse($describer->supports([$type]));
37+
}
38+
39+
public function testSupportsNoDifferentObjectPropertyType(): void
40+
{
41+
$type = new Type(Type::BUILTIN_TYPE_OBJECT, false, \DateTimeInterface::class);
42+
43+
$describer = new UuidPropertyDescriber();
44+
45+
self::assertFalse($describer->supports([$type]));
46+
}
47+
48+
public function testDescribeUuidPropertyType(): void
49+
{
50+
$property = $this->initProperty();
51+
$schema = $this->initSchema();
52+
53+
$describer = new UuidPropertyDescriber();
54+
$describer->describe([], $property, [], $schema);
55+
56+
self::assertSame('string', $property->type);
57+
self::assertSame('uuid', $property->format);
58+
}
59+
60+
private function initProperty(): \OpenApi\Annotations\Property
61+
{
62+
if (PHP_VERSION_ID < 80000) {
63+
return new \OpenApi\Annotations\Property([]);
64+
}
65+
66+
return new \OpenApi\Attributes\Property(); // union types, used in schema attribute require PHP >= 8.0.0
67+
}
68+
69+
private function initSchema(): \OpenApi\Annotations\Schema
70+
{
71+
if (PHP_VERSION_ID < 80000) {
72+
return new \OpenApi\Annotations\Schema([]);
73+
}
74+
75+
return new \OpenApi\Attributes\Schema(); // union types, used in schema attribute require PHP >= 8.0.0
76+
}
77+
}

0 commit comments

Comments
 (0)