From 4f20503cd226e1ea279bea634076b8836aa09088 Mon Sep 17 00:00:00 2001 From: Tomas Date: Thu, 25 Mar 2021 09:14:30 +0200 Subject: [PATCH] Fix generating property schema with Range restriction --- .../Bundle/Resources/config/validator.xml | 4 + .../PropertySchemaRangeRestriction.php | 57 ++++++++++++++ .../ApiPlatformExtensionTest.php | 1 + .../PropertySchemaRangeRestrictionTest.php | 74 +++++++++++++++++++ .../ValidatorPropertyMetadataFactoryTest.php | 39 ++++++++++ tests/Fixtures/DummyRangeValidatedEntity.php | 61 +++++++++++++++ 6 files changed, 236 insertions(+) create mode 100644 src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php create mode 100644 tests/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php create mode 100644 tests/Fixtures/DummyRangeValidatedEntity.php diff --git a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml b/src/Bridge/Symfony/Bundle/Resources/config/validator.xml index 77455124011..9a743ef093c 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/validator.xml @@ -26,6 +26,10 @@ + + + + diff --git a/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php new file mode 100644 index 00000000000..e2b2e1d84be --- /dev/null +++ b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestriction.php @@ -0,0 +1,57 @@ + + * + * 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\Bridge\Symfony\Validator\Metadata\Property\Restriction; + +use ApiPlatform\Core\Metadata\Property\PropertyMetadata; +use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Range; + +/** + * @author Tomas Norkūnas + */ +final class PropertySchemaRangeRestriction implements PropertySchemaRestrictionMetadataInterface +{ + /** + * {@inheritdoc} + */ + public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array + { + $restriction = []; + + switch ($propertyMetadata->getType()->getBuiltinType()) { + case Type::BUILTIN_TYPE_INT: + case Type::BUILTIN_TYPE_FLOAT: + if (isset($constraint->min) && is_numeric($constraint->min)) { + $restriction['minimum'] = $constraint->min; + } + + if (isset($constraint->max) && is_numeric($constraint->max)) { + $restriction['maximum'] = $constraint->max; + } + + break; + } + + return $restriction; + } + + /** + * {@inheritdoc} + */ + public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool + { + return $constraint instanceof Range && null !== $propertyMetadata->getType(); + } +} diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 0128896b095..846632920d6 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -1334,6 +1334,7 @@ private function getBaseContainerBuilderProphecyWithoutDefaultMetadataLoading(ar 'api_platform.metadata.property.metadata_factory.validator', 'api_platform.metadata.property_schema.length_restriction', 'api_platform.metadata.property_schema.one_of_restriction', + 'api_platform.metadata.property_schema.range_restriction', 'api_platform.metadata.property_schema.regex_restriction', 'api_platform.metadata.property_schema.format_restriction', 'api_platform.metadata.property.metadata_factory.yaml', diff --git a/tests/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php b/tests/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php new file mode 100644 index 00000000000..e5bfbc8d309 --- /dev/null +++ b/tests/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRangeRestrictionTest.php @@ -0,0 +1,74 @@ + + * + * 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\PropertySchemaRangeRestriction; +use ApiPlatform\Core\Metadata\Property\PropertyMetadata; +use ApiPlatform\Core\Tests\ProphecyTrait; +use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\Range; + +/** + * @author Tomas Norkūnas + */ +final class PropertySchemaRangeRestrictionTest extends TestCase +{ + use ProphecyTrait; + + private $propertySchemaRangeRestriction; + + protected function setUp(): void + { + $this->propertySchemaRangeRestriction = new PropertySchemaRangeRestriction(); + } + + /** + * @dataProvider supportsProvider + */ + public function testSupports(Constraint $constraint, PropertyMetadata $propertyMetadata, bool $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaRangeRestriction->supports($constraint, $propertyMetadata)); + } + + public function supportsProvider(): \Generator + { + yield 'supported int' => [new Range(['min' => 1, 'max' => 10]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT)), true]; + yield 'supported float' => [new Range(['min' => 1, 'max' => 10]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_FLOAT)), true]; + + yield 'not supported constraint' => [new Length(['min' => 1]), new PropertyMetadata(), false]; + yield 'not supported type' => [new Range(['min' => 1]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), true]; + } + + /** + * @dataProvider createProvider + */ + public function testCreate(Constraint $constraint, PropertyMetadata $propertyMetadata, array $expectedResult): void + { + self::assertSame($expectedResult, $this->propertySchemaRangeRestriction->create($constraint, $propertyMetadata)); + } + + public function createProvider(): \Generator + { + yield 'int min' => [new Range(['min' => 1]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT)), ['minimum' => 1]]; + yield 'int max' => [new Range(['max' => 10]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT)), ['maximum' => 10]]; + + yield 'float min' => [new Range(['min' => 1.5]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_FLOAT)), ['minimum' => 1.5]]; + yield 'float max' => [new Range(['max' => 10.5]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_FLOAT)), ['maximum' => 10.5]]; + + yield 'unsupported type' => [new Range(['min' => 1]), new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING)), []]; + } +} diff --git a/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php b/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php index f7c59d07666..5a05fcc8c07 100644 --- a/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php +++ b/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php @@ -16,12 +16,14 @@ use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaFormat; use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaLengthRestriction; use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaOneOfRestriction; +use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRangeRestriction; use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRegexRestriction; use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\ValidatorPropertyMetadataFactory; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\PropertyMetadata; use ApiPlatform\Core\Tests\Fixtures\DummyAtLeastOneOfValidatedEntity; use ApiPlatform\Core\Tests\Fixtures\DummyIriWithValidationEntity; +use ApiPlatform\Core\Tests\Fixtures\DummyRangeValidatedEntity; use ApiPlatform\Core\Tests\Fixtures\DummySequentiallyValidatedEntity; use ApiPlatform\Core\Tests\Fixtures\DummyValidatedEntity; use ApiPlatform\Core\Tests\ProphecyTrait; @@ -406,4 +408,41 @@ public function testCreateWithAtLeastOneOfConstraint(): void ['minLength' => 10], ], $schema['oneOf']); } + + /** + * @dataProvider provideRangeConstraintCases + */ + public function testCreateWithRangeConstraint(Type $type, string $property, array $expectedSchema): void + { + $validatorClassMetadata = new ClassMetadata(DummyRangeValidatedEntity::class); + (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata); + + $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class); + $validatorMetadataFactory->getMetadataFor(DummyRangeValidatedEntity::class) + ->willReturn($validatorClassMetadata) + ->shouldBeCalled(); + + $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); + $decoratedPropertyMetadataFactory->create(DummyRangeValidatedEntity::class, $property, [])->willReturn( + new PropertyMetadata($type) + )->shouldBeCalled(); + $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory( + $validatorMetadataFactory->reveal(), + $decoratedPropertyMetadataFactory->reveal(), + [new PropertySchemaRangeRestriction()] + ); + $schema = $validationPropertyMetadataFactory->create(DummyRangeValidatedEntity::class, $property)->getSchema(); + + $this->assertSame($expectedSchema, $schema); + } + + public function provideRangeConstraintCases(): \Generator + { + yield 'min int' => ['type' => new Type(Type::BUILTIN_TYPE_INT), 'property' => 'dummyIntMin', 'expectedSchema' => ['minimum' => 1]]; + yield 'max int' => ['type' => new Type(Type::BUILTIN_TYPE_INT), 'property' => 'dummyIntMax', 'expectedSchema' => ['maximum' => 10]]; + yield 'min/max int' => ['type' => new Type(Type::BUILTIN_TYPE_INT), 'property' => 'dummyIntMinMax', 'expectedSchema' => ['minimum' => 1, 'maximum' => 10]]; + yield 'min float' => ['type' => new Type(Type::BUILTIN_TYPE_FLOAT), 'property' => 'dummyFloatMin', 'expectedSchema' => ['minimum' => 1.5]]; + yield 'max float' => ['type' => new Type(Type::BUILTIN_TYPE_FLOAT), 'property' => 'dummyFloatMax', 'expectedSchema' => ['maximum' => 10.5]]; + yield 'min/max float' => ['type' => new Type(Type::BUILTIN_TYPE_FLOAT), 'property' => 'dummyFloatMinMax', 'expectedSchema' => ['minimum' => 1.5, 'maximum' => 10.5]]; + } } diff --git a/tests/Fixtures/DummyRangeValidatedEntity.php b/tests/Fixtures/DummyRangeValidatedEntity.php new file mode 100644 index 00000000000..5a2a9a0cc5a --- /dev/null +++ b/tests/Fixtures/DummyRangeValidatedEntity.php @@ -0,0 +1,61 @@ + + * + * 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 DummyRangeValidatedEntity +{ + /** + * @var int + * + * @Assert\Range(min=1) + */ + public $dummyIntMin; + + /** + * @var int + * + * @Assert\Range(max=10) + */ + public $dummyIntMax; + + /** + * @var int + * + * @Assert\Range(min=1, max=10) + */ + public $dummyIntMinMax; + + /** + * @var float + * + * @Assert\Range(min=1.5) + */ + public $dummyFloatMin; + + /** + * @var float + * + * @Assert\Range(max=10.5) + */ + public $dummyFloatMax; + + /** + * @var float + * + * @Assert\Range(min=1.5, max=10.5) + */ + public $dummyFloatMinMax; +}