From 989df9a3dceacd4a5b0c7e1d4f2ed8af118aaee2 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 2 Apr 2021 10:02:26 +0300 Subject: [PATCH] Add support for generating property schema format for Url and Hostname --- CHANGELOG.md | 1 + .../Restriction/PropertySchemaFormat.php | 12 ++- .../Restriction/PropertySchemaFormatTest.php | 86 +++++++++++++++++++ .../ValidatorPropertyMetadataFactoryTest.php | 59 ++++++++----- tests/Fixtures/DummyValidatedEntity.php | 7 ++ .../Fixtures/DummyValidatedHostnameEntity.php | 26 ++++++ tests/Fixtures/DummyValidatedUlidEntity.php | 26 ++++++ 7 files changed, 193 insertions(+), 24 deletions(-) create mode 100644 tests/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php create mode 100644 tests/Fixtures/DummyValidatedHostnameEntity.php create mode 100644 tests/Fixtures/DummyValidatedUlidEntity.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6db4ecef9a3..72a3f3fa28a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 2.7.0 +* JSON Schema: Add support for generating property schema format for Url and Hostname (#4185) * JSON Schema: Add support for generating property schema with Count restriction (#4186) * JSON Schema: Manage Compound constraint when generating property metadata (#4180) * Validator: Add an option to disable query parameter validation (#4165) diff --git a/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php index c6cec232b6f..b77c9642045 100644 --- a/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php +++ b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php @@ -16,8 +16,10 @@ use ApiPlatform\Core\Metadata\Property\PropertyMetadata; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\Hostname; use Symfony\Component\Validator\Constraints\Ip; use Symfony\Component\Validator\Constraints\Ulid; +use Symfony\Component\Validator\Constraints\Url; use Symfony\Component\Validator\Constraints\Uuid; /** @@ -36,6 +38,14 @@ public function create(Constraint $constraint, PropertyMetadata $propertyMetadat return ['format' => 'email']; } + if ($constraint instanceof Url) { + return ['format' => 'uri']; + } + + if ($constraint instanceof Hostname) { + return ['format' => 'hostname']; + } + if ($constraint instanceof Uuid) { return ['format' => 'uuid']; } @@ -62,6 +72,6 @@ public function supports(Constraint $constraint, PropertyMetadata $propertyMetad { $schema = $propertyMetadata->getSchema(); - return empty($schema['format']) && ($constraint instanceof Email || $constraint instanceof Uuid || $constraint instanceof Ulid || $constraint instanceof Ip); + return empty($schema['format']) && ($constraint instanceof Email || $constraint instanceof Url || $constraint instanceof Hostname || $constraint instanceof Uuid || $constraint instanceof Ulid || $constraint instanceof Ip); } } diff --git a/tests/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php b/tests/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php new file mode 100644 index 00000000000..f8c67873c8c --- /dev/null +++ b/tests/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormatTest.php @@ -0,0 +1,86 @@ + + * + * 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\Core\Tests\Bridge\Symfony\Validator\Metadata\Property\Restriction; + +use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaFormat; +use ApiPlatform\Core\Metadata\Property\PropertyMetadata; +use ApiPlatform\Core\Tests\ProphecyTrait; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\Hostname; +use Symfony\Component\Validator\Constraints\Ip; +use Symfony\Component\Validator\Constraints\Positive; +use Symfony\Component\Validator\Constraints\Ulid; +use Symfony\Component\Validator\Constraints\Url; +use Symfony\Component\Validator\Constraints\Uuid; + +final class PropertySchemaFormatTest extends TestCase +{ + use ProphecyTrait; + + private $propertySchemaFormatRestriction; + + protected function setUp(): void + { + $this->propertySchemaFormatRestriction = new PropertySchemaFormat(); + } + + /** + * @dataProvider supportsProvider + */ + public function testSupports(Constraint $constraint, PropertyMetadata $propertyMetadata, bool $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaFormatRestriction->supports($constraint, $propertyMetadata)); + } + + public function supportsProvider(): \Generator + { + yield 'email' => [new Email(), new PropertyMetadata(), true]; + yield 'url' => [new Url(), new PropertyMetadata(), true]; + if (class_exists(Hostname::class)) { + yield 'hostname' => [new Hostname(), new PropertyMetadata(), true]; + } + yield 'uuid' => [new Uuid(), new PropertyMetadata(), true]; + if (class_exists(Ulid::class)) { + yield 'ulid' => [new Ulid(), new PropertyMetadata(), true]; + } + yield 'ip' => [new Ip(), new PropertyMetadata(), true]; + yield 'not supported' => [new Positive(), new PropertyMetadata(), false]; + } + + /** + * @dataProvider createProvider + */ + public function testCreate(Constraint $constraint, PropertyMetadata $propertyMetadata, array $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaFormatRestriction->create($constraint, $propertyMetadata)); + } + + public function createProvider(): \Generator + { + yield 'email' => [new Email(), new PropertyMetadata(), ['format' => 'email']]; + yield 'url' => [new Url(), new PropertyMetadata(), ['format' => 'uri']]; + if (class_exists(Hostname::class)) { + yield 'hostname' => [new Hostname(), new PropertyMetadata(), ['format' => 'hostname']]; + } + yield 'uuid' => [new Uuid(), new PropertyMetadata(), ['format' => 'uuid']]; + if (class_exists(Ulid::class)) { + yield 'ulid' => [new Ulid(), new PropertyMetadata(), ['format' => 'ulid']]; + } + yield 'ipv4' => [new Ip(['version' => '4']), new PropertyMetadata(), ['format' => 'ipv4']]; + yield 'ipv6' => [new Ip(['version' => '6']), new PropertyMetadata(), ['format' => 'ipv6']]; + yield 'not supported' => [new Positive(), new PropertyMetadata(), []]; + } +} diff --git a/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php b/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php index fb4c484c895..ec831cd4982 100644 --- a/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php +++ b/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php @@ -33,13 +33,17 @@ use ApiPlatform\Core\Tests\Fixtures\DummyUniqueValidatedEntity; use ApiPlatform\Core\Tests\Fixtures\DummyValidatedChoiceEntity; use ApiPlatform\Core\Tests\Fixtures\DummyValidatedEntity; +use ApiPlatform\Core\Tests\Fixtures\DummyValidatedHostnameEntity; +use ApiPlatform\Core\Tests\Fixtures\DummyValidatedUlidEntity; use ApiPlatform\Core\Tests\ProphecyTrait; use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Validator\Constraints\AtLeastOneOf; use Symfony\Component\Validator\Constraints\Compound; +use Symfony\Component\Validator\Constraints\Hostname; use Symfony\Component\Validator\Constraints\Sequentially; +use Symfony\Component\Validator\Constraints\Ulid; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; @@ -318,36 +322,45 @@ public function testCreateWithPropertyRegexRestriction(): void $this->assertEquals('dummy', $schema['pattern']); } - public function testCreateWithPropertyFormatRestriction(): void + /** + * @dataProvider providePropertySchemaFormatCases + */ + public function testCreateWithPropertyFormatRestriction(string $property, string $class, array $expectedSchema): void { - $validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class); + $validatorClassMetadata = new ClassMetadata($class); (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata); $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); - $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class) + $validatorMetadataFactory->getMetadataFor($class) ->willReturn($validatorClassMetadata) ->shouldBeCalled(); - $formats = [ - 'dummyEmail' => 'email', - 'dummyUuid' => 'uuid', - 'dummyIpv4' => 'ipv4', - 'dummyIpv6' => 'ipv6', - ]; - foreach ($formats as $property => $format) { - $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); - $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, $property, [])->willReturn( - new PropertyMetadata() - )->shouldBeCalled(); - $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( - $validatorMetadataFactory->reveal(), - $decoratedPropertyMetadataFactory->reveal(), - [new PropertySchemaFormat()] - ); - $schema = $validationPropertyMetadataFactory->create(DummyValidatedEntity::class, $property)->getSchema(); - $this->assertNotNull($schema); - $this->assertArrayHasKey('format', $schema); - $this->assertEquals($format, $schema['format']); + $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); + $decoratedPropertyMetadataFactory->create($class, $property, [])->willReturn( + new PropertyMetadata() + )->shouldBeCalled(); + $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( + $validatorMetadataFactory->reveal(), + $decoratedPropertyMetadataFactory->reveal(), + [new PropertySchemaFormat()] + ); + $schema = $validationPropertyMetadataFactory->create($class, $property)->getSchema(); + + $this->assertSame($expectedSchema, $schema); + } + + public function providePropertySchemaFormatCases(): \Generator + { + yield ['dummyEmail', DummyValidatedEntity::class, ['format' => 'email']]; + yield ['dummyUuid', DummyValidatedEntity::class, ['format' => 'uuid']]; + yield ['dummyIpv4', DummyValidatedEntity::class, ['format' => 'ipv4']]; + yield ['dummyIpv6', DummyValidatedEntity::class, ['format' => 'ipv6']]; + yield ['dummyUrl', DummyValidatedEntity::class, ['format' => 'uri']]; + if (class_exists(Ulid::class)) { + yield ['dummyUlid', DummyValidatedUlidEntity::class, ['format' => 'ulid']]; + } + if (class_exists(Hostname::class)) { + yield ['dummyHostname', DummyValidatedHostnameEntity::class, ['format' => 'hostname']]; } } diff --git a/tests/Fixtures/DummyValidatedEntity.php b/tests/Fixtures/DummyValidatedEntity.php index 3668c7ff51b..1e287372193 100644 --- a/tests/Fixtures/DummyValidatedEntity.php +++ b/tests/Fixtures/DummyValidatedEntity.php @@ -77,4 +77,11 @@ class DummyValidatedEntity * @Assert\NotNull(groups={"dummy"}) */ public $dummyGroup; + + /** + * @var string A dummy url + * + * @Assert\Url + */ + public $dummyUrl; } diff --git a/tests/Fixtures/DummyValidatedHostnameEntity.php b/tests/Fixtures/DummyValidatedHostnameEntity.php new file mode 100644 index 00000000000..dcf0306dbde --- /dev/null +++ b/tests/Fixtures/DummyValidatedHostnameEntity.php @@ -0,0 +1,26 @@ + + * + * 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\Core\Tests\Fixtures; + +use Symfony\Component\Validator\Constraints as Assert; + +class DummyValidatedHostnameEntity +{ + /** + * @var string + * + * @Assert\Hostname + */ + public $dummyHostname; +} diff --git a/tests/Fixtures/DummyValidatedUlidEntity.php b/tests/Fixtures/DummyValidatedUlidEntity.php new file mode 100644 index 00000000000..0600249eae0 --- /dev/null +++ b/tests/Fixtures/DummyValidatedUlidEntity.php @@ -0,0 +1,26 @@ + + * + * 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\Core\Tests\Fixtures; + +use Symfony\Component\Validator\Constraints as Assert; + +class DummyValidatedUlidEntity +{ + /** + * @var string + * + * @Assert\Ulid + */ + public $dummyUlid; +}