Skip to content

Commit

Permalink
feat: added symfony/uuid property describer (#2098)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
stollr and DjordyKoert authored Jun 12, 2024
1 parent 599283f commit 221a1fe
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 0 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
4 changes: 4 additions & 0 deletions config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@
<tag name="nelmio_api_doc.object_model.property_describer" priority="-1000" />
</service>

<service id="nelmio_api_doc.object_model.property_describers.uuid" class="Nelmio\ApiDocBundle\PropertyDescriber\UuidPropertyDescriber" public="false">
<tag name="nelmio_api_doc.object_model.property_describer" />
</service>

<!-- Form Type Extensions -->

<service id="nelmio_api_doc.form.documentation_extension" class="Nelmio\ApiDocBundle\Form\Extension\DocumentationExtension">
Expand Down
4 changes: 4 additions & 0 deletions src/DependencyInjection/NelmioApiDocExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
32 changes: 32 additions & 0 deletions src/PropertyDescriber/UuidPropertyDescriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\PropertyDescriber;

use OpenApi\Annotations as OA;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Uid\AbstractUid;

final class UuidPropertyDescriber implements PropertyDescriberInterface
{
public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null)
{
$property->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);
}
}
12 changes: 12 additions & 0 deletions tests/Functional/Controller/ApiController80.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"})
*
Expand Down
13 changes: 13 additions & 0 deletions tests/Functional/Controller/ApiController81.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
26 changes: 26 additions & 0 deletions tests/Functional/Entity/EntityWithUuid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;

use Symfony\Component\Uid\Uuid;

class EntityWithUuid
{
public Uuid $id;
public string $name;

public function __construct(string $name)
{
$this->id = Uuid::v1();
$this->name = $name;
}
}
18 changes: 18 additions & 0 deletions tests/Functional/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
77 changes: 77 additions & 0 deletions tests/PropertyDescriber/UuidPropertyDescriberTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\Tests\PropertyDescriber;

use Nelmio\ApiDocBundle\PropertyDescriber\UuidPropertyDescriber;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Uid\Uuid;

class UuidPropertyDescriberTest extends TestCase
{
public function testSupportsUuidPropertyType(): void
{
$type = new Type(Type::BUILTIN_TYPE_OBJECT, false, Uuid::class);

$describer = new UuidPropertyDescriber();

self::assertTrue($describer->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
}
}

0 comments on commit 221a1fe

Please sign in to comment.