diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
index de9a2031c03..af6d2514150 100644
--- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
+++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php
@@ -23,6 +23,7 @@
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter as DoctrineOrmAbstractContextAwareFilter;
use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension\RequestBodySearchCollectionExtensionInterface;
+use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
@@ -553,6 +554,8 @@ private function registerValidatorConfiguration(ContainerBuilder $container, arr
{
if (interface_exists(ValidatorInterface::class)) {
$loader->load('validator.xml');
+ $container->registerForAutoconfiguration(PropertySchemaRestrictionMetadataInterface::class)
+ ->addTag('api_platform.metadata.property_schema_restriction');
}
if (!$config['validator']) {
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml b/src/Bridge/Symfony/Bundle/Resources/config/validator.xml
index 2526c93cfc6..9a5a50a6d7e 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/validator.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/validator.xml
@@ -14,6 +14,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php
new file mode 100644
index 00000000000..67990bfcab5
--- /dev/null
+++ b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaFormat.php
@@ -0,0 +1,62 @@
+
+ *
+ * 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\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\Email;
+use Symfony\Component\Validator\Constraints\Ip;
+use Symfony\Component\Validator\Constraints\Uuid;
+
+/**
+ * Class PropertySchemaFormat.
+ *
+ * @author Andrii Penchuk penja7@gmail.com
+ */
+class PropertySchemaFormat implements PropertySchemaRestrictionMetadataInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array
+ {
+ if ($constraint instanceof Email) {
+ return ['format' => 'email'];
+ }
+
+ if ($constraint instanceof Uuid) {
+ return ['format' => 'uuid'];
+ }
+
+ if ($constraint instanceof Ip) {
+ if ($constraint->version === $constraint::V4) {
+ return ['format' => 'ipv4'];
+ }
+
+ return ['format' => 'ipv6'];
+ }
+
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool
+ {
+ $schema = $propertyMetadata->getSchema();
+
+ return empty($schema['format']);
+ }
+}
diff --git a/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php
new file mode 100644
index 00000000000..c6c2a7abffa
--- /dev/null
+++ b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaLengthRestriction.php
@@ -0,0 +1,70 @@
+
+ *
+ * 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\Length;
+
+/**
+ * Class PropertySchemaLengthRestrictions.
+ *
+ * @author Andrii Penchuk penja7@gmail.com
+ */
+class PropertySchemaLengthRestriction implements PropertySchemaRestrictionMetadataInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array
+ {
+ $restriction = [];
+
+ switch ($propertyMetadata->getType()->getBuiltinType()) {
+ case Type::BUILTIN_TYPE_STRING:
+
+ if (isset($constraint->min)) {
+ $restriction['minLength'] = (int) $constraint->min;
+ }
+
+ if (isset($constraint->max)) {
+ $restriction['maxLength'] = (int) $constraint->max;
+ }
+
+ break;
+ case Type::BUILTIN_TYPE_INT:
+ case Type::BUILTIN_TYPE_FLOAT:
+ if (isset($constraint->min)) {
+ $restriction['minimum'] = (int) $constraint->min;
+ }
+
+ if (isset($constraint->max)) {
+ $restriction['maximum'] = (int) $constraint->max;
+ }
+
+ break;
+ }
+
+ return $restriction;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool
+ {
+ return $constraint instanceof Length && null !== $propertyMetadata->getType();
+ }
+}
diff --git a/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestriction.php b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestriction.php
new file mode 100644
index 00000000000..dccc81e9fbd
--- /dev/null
+++ b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRegexRestriction.php
@@ -0,0 +1,42 @@
+
+ *
+ * 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\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\Regex;
+
+/**
+ * Class PropertySchemaRegexRestriction.
+ *
+ * @author Andrii Penchuk penja7@gmail.com
+ */
+class PropertySchemaRegexRestriction implements PropertySchemaRestrictionMetadataInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array
+ {
+ return isset($constraint->pattern) ? ['pattern' => $constraint->pattern] : [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool
+ {
+ return $constraint instanceof Regex && $constraint->match;
+ }
+}
diff --git a/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRestrictionMetadataInterface.php b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRestrictionMetadataInterface.php
new file mode 100644
index 00000000000..2486342de40
--- /dev/null
+++ b/src/Bridge/Symfony/Validator/Metadata/Property/Restriction/PropertySchemaRestrictionMetadataInterface.php
@@ -0,0 +1,43 @@
+
+ *
+ * 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\Validator\Constraint;
+
+/**
+ * Interface PropertySchemaRestrictionsInterface.
+ *
+ * @author Andrii Penchuk penja7@gmail.com
+ */
+interface PropertySchemaRestrictionMetadataInterface
+{
+ /**
+ * Creates json schema restrictions based on the validation constraints.
+ *
+ * @param Constraint $constraint The validation constraint
+ * @param PropertyMetadata $propertyMetadata The property metadata
+ *
+ * @return array The array of restrictions
+ */
+ public function create(Constraint $constraint, PropertyMetadata $propertyMetadata): array;
+
+ /**
+ * Is the constraint supported by the schema restriction?
+ *
+ * @param Constraint $constraint The validation constraint
+ * @param PropertyMetadata $propertyMetadata The property metadata
+ */
+ public function supports(Constraint $constraint, PropertyMetadata $propertyMetadata): bool;
+}
diff --git a/src/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php b/src/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php
index 5568720c592..438f20d1a2d 100644
--- a/src/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php
+++ b/src/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactory.php
@@ -13,6 +13,7 @@
namespace ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property;
+use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
use Symfony\Component\Validator\Constraint;
@@ -67,11 +68,19 @@ final class ValidatorPropertyMetadataFactory implements PropertyMetadataFactoryI
private $decorated;
private $validatorMetadataFactory;
+ /**
+ * @var iterable
+ */
+ private $restrictionsMetadata;
- public function __construct(ValidatorMetadataFactoryInterface $validatorMetadataFactory, PropertyMetadataFactoryInterface $decorated)
+ /**
+ * @param PropertySchemaRestrictionMetadataInterface[] $restrictionsMetadata
+ */
+ public function __construct(ValidatorMetadataFactoryInterface $validatorMetadataFactory, PropertyMetadataFactoryInterface $decorated, iterable $restrictionsMetadata = [])
{
$this->validatorMetadataFactory = $validatorMetadataFactory;
$this->decorated = $decorated;
+ $this->restrictionsMetadata = $restrictionsMetadata;
}
/**
@@ -83,26 +92,23 @@ public function create(string $resourceClass, string $name, array $options = [])
$required = $propertyMetadata->isRequired();
$iri = $propertyMetadata->getIri();
+ $schema = $propertyMetadata->getSchema();
- if (null !== $required && null !== $iri) {
+ if (null !== $required && null !== $iri && null !== $schema) {
return $propertyMetadata;
}
$validatorClassMetadata = $this->validatorMetadataFactory->getMetadataFor($resourceClass);
+
if (!$validatorClassMetadata instanceof ValidatorClassMetadataInterface) {
throw new \UnexpectedValueException(sprintf('Validator class metadata expected to be of type "%s".', ValidatorClassMetadataInterface::class));
}
- foreach ($validatorClassMetadata->getPropertyMetadata($name) as $validatorPropertyMetadata) {
- if (null === $required && isset($options['validation_groups'])) {
- $required = $this->isRequiredByGroups($validatorPropertyMetadata, $options);
- }
-
- if (!method_exists($validatorClassMetadata, 'getDefaultGroup')) {
- throw new \UnexpectedValueException(sprintf('Validator class metadata expected to have method "%s".', 'getDefaultGroup'));
- }
+ $validationGroups = $this->getValidationGroups($validatorClassMetadata, $options);
+ $restrictions = [];
- foreach ($validatorPropertyMetadata->findConstraints($validatorClassMetadata->getDefaultGroup()) as $constraint) {
+ foreach ($validatorClassMetadata->getPropertyMetadata($name) as $validatorPropertyMetadata) {
+ foreach ($this->getPropertyConstraints($validatorPropertyMetadata, $validationGroups) as $constraint) {
if (null === $required && $this->isRequired($constraint)) {
$required = true;
}
@@ -111,33 +117,64 @@ public function create(string $resourceClass, string $name, array $options = [])
$iri = self::SCHEMA_MAPPED_CONSTRAINTS[\get_class($constraint)] ?? null;
}
- if (null !== $required && null !== $iri) {
- break 2;
+ foreach ($this->restrictionsMetadata as $restrictionMetadata) {
+ if ($restrictionMetadata->supports($constraint, $propertyMetadata)) {
+ $restrictions[] = $restrictionMetadata->create($constraint, $propertyMetadata);
+ }
}
}
}
- return $propertyMetadata->withIri($iri)->withRequired($required ?? false);
+ $propertyMetadata = $propertyMetadata->withIri($iri)->withRequired($required ?? false);
+
+ if (!empty($restrictions)) {
+ if (null === $schema) {
+ $schema = [];
+ }
+
+ $schema += array_merge(...$restrictions);
+ $propertyMetadata = $propertyMetadata->withSchema($schema);
+ }
+
+ return $propertyMetadata;
}
/**
- * Tests if the property is required because of its validation groups.
+ * Returns the list of validation groups.
*/
- private function isRequiredByGroups(ValidatorPropertyMetadataInterface $validatorPropertyMetadata, array $options): bool
+ private function getValidationGroups(ValidatorClassMetadataInterface $classMetadata, array $options): array
{
- foreach ($options['validation_groups'] as $validationGroup) {
+ if (isset($options['validation_groups'])) {
+ return $options['validation_groups'];
+ }
+
+ if (!method_exists($classMetadata, 'getDefaultGroup')) {
+ throw new \UnexpectedValueException(sprintf('Validator class metadata expected to have method "%s".', 'getDefaultGroup'));
+ }
+
+ return [$classMetadata->getDefaultGroup()];
+ }
+
+ /**
+ * Tests if the property is required because of its validation groups.
+ */
+ private function getPropertyConstraints(
+ ValidatorPropertyMetadataInterface $validatorPropertyMetadata,
+ array $groups
+ ): array {
+ $constraints = [];
+
+ foreach ($groups as $validationGroup) {
if (!\is_string($validationGroup)) {
continue;
}
- foreach ($validatorPropertyMetadata->findConstraints($validationGroup) as $constraint) {
- if ($this->isRequired($constraint)) {
- return true;
- }
+ foreach ($validatorPropertyMetadata->findConstraints($validationGroup) as $propertyConstraint) {
+ $constraints[] = $propertyConstraint;
}
}
- return false;
+ return $constraints;
}
/**
diff --git a/src/JsonSchema/SchemaFactory.php b/src/JsonSchema/SchemaFactory.php
index fe673a83bfc..8790b6af5da 100644
--- a/src/JsonSchema/SchemaFactory.php
+++ b/src/JsonSchema/SchemaFactory.php
@@ -153,6 +153,8 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
{
$version = $schema->getVersion();
$swagger = false;
+ $propertySchema = $propertyMetadata->getSchema() ?? [];
+
switch ($version) {
case Schema::VERSION_SWAGGER:
$swagger = true;
@@ -165,7 +167,11 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
$basePropertySchemaAttribute = 'json_schema_context';
}
- $propertySchema = $propertyMetadata->getAttributes()[$basePropertySchemaAttribute] ?? [];
+ $propertySchema = array_merge(
+ $propertySchema,
+ $propertyMetadata->getAttributes()[$basePropertySchemaAttribute] ?? []
+ );
+
if (false === $propertyMetadata->isWritable() && !$propertyMetadata->isInitializable()) {
$propertySchema['readOnly'] = true;
}
diff --git a/src/Metadata/Property/PropertyMetadata.php b/src/Metadata/Property/PropertyMetadata.php
index 1018d9726ec..4629b89e291 100644
--- a/src/Metadata/Property/PropertyMetadata.php
+++ b/src/Metadata/Property/PropertyMetadata.php
@@ -46,8 +46,9 @@ final class PropertyMetadata
* @var null
*/
private $example;
+ private $schema;
- public function __construct(Type $type = null, string $description = null, bool $readable = null, bool $writable = null, bool $readableLink = null, bool $writableLink = null, bool $required = null, bool $identifier = null, string $iri = null, $childInherited = null, array $attributes = null, SubresourceMetadata $subresource = null, bool $initializable = null, $default = null, $example = null)
+ public function __construct(Type $type = null, string $description = null, bool $readable = null, bool $writable = null, bool $readableLink = null, bool $writableLink = null, bool $required = null, bool $identifier = null, string $iri = null, $childInherited = null, array $attributes = null, SubresourceMetadata $subresource = null, bool $initializable = null, $default = null, $example = null, array $schema = null)
{
$this->type = $type;
$this->description = $description;
@@ -67,6 +68,7 @@ public function __construct(Type $type = null, string $description = null, bool
$this->initializable = $initializable;
$this->default = $default;
$this->example = $example;
+ $this->schema = $schema;
}
/**
@@ -397,4 +399,23 @@ public function withExample($example): self
return $metadata;
}
+
+ /**
+ * @return array
+ */
+ public function getSchema(): ?array
+ {
+ return $this->schema;
+ }
+
+ /**
+ * Returns a new instance with the given schema.
+ */
+ public function withSchema(array $schema = null): self
+ {
+ $metadata = clone $this;
+ $metadata->schema = $schema;
+
+ return $metadata;
+ }
}
diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
index 604c1d508eb..a9dc1fdb20c 100644
--- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
+++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php
@@ -55,6 +55,7 @@
use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter\TermFilter;
use ApiPlatform\Core\Bridge\Elasticsearch\Metadata\Document\Factory\DocumentMetadataFactoryInterface;
use ApiPlatform\Core\Bridge\Symfony\Bundle\DependencyInjection\ApiPlatformExtension;
+use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\Restriction\PropertySchemaRestrictionMetadataInterface;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
@@ -1089,6 +1090,10 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo
->willReturn($this->childDefinitionProphecy)->shouldBeCalledTimes(1);
$this->childDefinitionProphecy->setBindings(['$requestStack' => null])->shouldBeCalledTimes(1);
+ $containerBuilderProphecy->registerForAutoconfiguration(PropertySchemaRestrictionMetadataInterface::class)
+ ->willReturn($this->childDefinitionProphecy)->shouldBeCalledTimes(1);
+ $this->childDefinitionProphecy->addTag('api_platform.metadata.property_schema_restriction')->shouldBeCalledTimes(1);
+
if (\in_array('odm', $doctrineIntegrationsToLoad, true)) {
$containerBuilderProphecy->registerForAutoconfiguration(AggregationItemExtensionInterface::class)
->willReturn($this->childDefinitionProphecy)->shouldBeCalledTimes(1);
@@ -1231,6 +1236,9 @@ private function getBaseContainerBuilderProphecy(array $doctrineIntegrationsToLo
'api_platform.metadata.extractor.yaml',
'api_platform.metadata.property.metadata_factory.annotation',
'api_platform.metadata.property.metadata_factory.validator',
+ 'api_platform.metadata.property_schema.length_restriction',
+ 'api_platform.metadata.property_schema.regex_restriction',
+ 'api_platform.metadata.property_schema.format_restriction',
'api_platform.metadata.property.metadata_factory.yaml',
'api_platform.metadata.property.name_collection_factory.yaml',
'api_platform.metadata.resource.filter_metadata_factory.annotation',
diff --git a/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php b/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php
index 3ae3a29a27e..1e989499ecd 100644
--- a/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php
+++ b/tests/Bridge/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php
@@ -13,6 +13,9 @@
namespace ApiPlatform\Core\Tests\Bridge\Symfony\Validator\Metadata\Property;
+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\PropertySchemaRegexRestriction;
use ApiPlatform\Core\Bridge\Symfony\Validator\Metadata\Property\ValidatorPropertyMetadataFactory;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
@@ -20,6 +23,7 @@
use ApiPlatform\Core\Tests\Fixtures\DummyValidatedEntity;
use Doctrine\Common\Annotations\AnnotationReader;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
@@ -48,7 +52,11 @@ public function testCreateWithPropertyWithRequiredConstraints()
$validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
$validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled();
- $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory($validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal());
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(),
+ $decoratedPropertyMetadataFactory->reveal(),
+ []
+ );
$resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummy');
$this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata);
@@ -66,7 +74,11 @@ public function testCreateWithPropertyWithNotRequiredConstraints()
$validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
$validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled();
- $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory($validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal());
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(),
+ $decoratedPropertyMetadataFactory->reveal(),
+ []
+ );
$resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyDate');
$this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata);
@@ -83,7 +95,11 @@ public function testCreateWithPropertyWithoutConstraints()
$validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
$validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled();
- $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory($validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal());
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(),
+ $decoratedPropertyMetadataFactory->reveal(),
+ []
+ );
$resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyId');
$this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata);
@@ -100,7 +116,11 @@ public function testCreateWithPropertyWithRightValidationGroupsAndRequiredConstr
$validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
$validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled();
- $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory($validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal());
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(),
+ $decoratedPropertyMetadataFactory->reveal(),
+ []
+ );
$resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyGroup', ['validation_groups' => ['dummy']]);
$this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata);
@@ -117,7 +137,11 @@ public function testCreateWithPropertyWithBadValidationGroupsAndRequiredConstrai
$validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
$validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled();
- $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory($validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal());
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(),
+ $decoratedPropertyMetadataFactory->reveal(),
+ []
+ );
$resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyGroup', ['validation_groups' => ['ymmud']]);
$this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata);
@@ -134,7 +158,11 @@ public function testCreateWithPropertyWithNonStringValidationGroupsAndRequiredCo
$validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
$validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled();
- $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory($validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal());
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(),
+ $decoratedPropertyMetadataFactory->reveal(),
+ []
+ );
$resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyGroup', ['validation_groups' => [1312]]);
$this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata);
@@ -149,8 +177,13 @@ public function testCreateWithRequiredByDecorated()
$decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyDate', [])->willReturn($propertyMetadata)->shouldBeCalled();
$validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
+ $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)->willReturn($this->validatorClassMetadata)->shouldBeCalled();
- $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory($validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal());
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(),
+ $decoratedPropertyMetadataFactory->reveal(),
+ []
+ );
$resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummyDate');
$this->assertEquals($expectedPropertyMetadata, $resultedPropertyMetadata);
@@ -186,11 +219,117 @@ public function testCreateWithPropertyWithValidationConstraints()
$validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
$validatorMetadataFactory->getMetadataFor(DummyIriWithValidationEntity::class)->willReturn($validatorClassMetadata)->shouldBeCalled();
- $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory($validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal());
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(),
+ $decoratedPropertyMetadataFactory->reveal(),
+ []
+ );
foreach ($types as $property => $iri) {
$resultedPropertyMetadata = $validatorPropertyMetadataFactory->create(DummyIriWithValidationEntity::class, $property);
$this->assertSame($iri, $resultedPropertyMetadata->getIri());
}
}
+
+ public function testCreateWithPropertyLengthRestriction(): void
+ {
+ $validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class);
+ (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata);
+
+ $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
+ $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)
+ ->willReturn($validatorClassMetadata)
+ ->shouldBeCalled();
+
+ $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
+ $property = 'dummy';
+ $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, $property, [])->willReturn(
+ new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING))
+ )->shouldBeCalled();
+
+ $lengthRestrictions = new PropertySchemaLengthRestriction();
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal(), [$lengthRestrictions]
+ );
+
+ $schema = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, $property)->getSchema();
+ $this->assertNotNull($schema);
+ $this->assertArrayHasKey('minLength', $schema);
+ $this->assertArrayHasKey('maxLength', $schema);
+
+ $numberTypes = [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT];
+
+ foreach ($numberTypes as $type) {
+ $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, $property, [])->willReturn(
+ new PropertyMetadata(new Type($type))
+ )->shouldBeCalled();
+ $validatorPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal(), [$lengthRestrictions]
+ );
+
+ $schema = $validatorPropertyMetadataFactory->create(DummyValidatedEntity::class, $property)->getSchema();
+ $this->assertNotNull($schema);
+ $this->assertArrayHasKey('minimum', $schema);
+ $this->assertArrayHasKey('maximum', $schema);
+ }
+ }
+
+ public function testCreateWithPropertyRegexRestriction(): void
+ {
+ $validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class);
+ (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata);
+
+ $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
+ $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::class)
+ ->willReturn($validatorClassMetadata)
+ ->shouldBeCalled();
+
+ $decoratedPropertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class);
+ $decoratedPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummy', [])->willReturn(
+ new PropertyMetadata()
+ )->shouldBeCalled();
+
+ $validationPropertyMetadataFactory = new ValidatorPropertyMetadataFactory(
+ $validatorMetadataFactory->reveal(), $decoratedPropertyMetadataFactory->reveal(),
+ [new PropertySchemaRegexRestriction()]
+ );
+
+ $schema = $validationPropertyMetadataFactory->create(DummyValidatedEntity::class, 'dummy')->getSchema();
+ $this->assertNotNull($schema);
+ $this->assertArrayHasKey('pattern', $schema);
+ $this->assertEquals('^dummy$', $schema['pattern']);
+ }
+
+ public function testCreateWithPropertyFormatRestriction(): void
+ {
+ $validatorClassMetadata = new ClassMetadata(DummyValidatedEntity::class);
+ (new AnnotationLoader(new AnnotationReader()))->loadClassMetadata($validatorClassMetadata);
+
+ $validatorMetadataFactory = $this->prophesize(MetadataFactoryInterface::class);
+ $validatorMetadataFactory->getMetadataFor(DummyValidatedEntity::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']);
+ }
+ }
}
diff --git a/tests/Fixtures/DummyValidatedEntity.php b/tests/Fixtures/DummyValidatedEntity.php
index 8314774b654..81d3e4b852d 100644
--- a/tests/Fixtures/DummyValidatedEntity.php
+++ b/tests/Fixtures/DummyValidatedEntity.php
@@ -31,9 +31,39 @@ class DummyValidatedEntity
* @var string A dummy
*
* @Assert\NotBlank
+ * @Assert\Length(max="4", min="10")
+ * @Assert\Regex(pattern="^dummy$")
*/
public $dummy;
+ /**
+ * @var string
+ *
+ * @Assert\Email
+ */
+ public $dummyEmail;
+
+ /**
+ * @var string
+ *
+ * @Assert\Uuid
+ */
+ public $dummyUuid;
+
+ /**
+ * @var string
+ *
+ * @Assert\Ip
+ */
+ public $dummyIpv4;
+
+ /**
+ * @var string
+ *
+ * @Assert\Ip(version="6")
+ */
+ public $dummyIpv6;
+
/**
* @var \DateTimeInterface A dummy date
*
diff --git a/tests/Swagger/Serializer/DocumentationNormalizerV3Test.php b/tests/Swagger/Serializer/DocumentationNormalizerV3Test.php
index d6250fb3b90..92499eafe50 100644
--- a/tests/Swagger/Serializer/DocumentationNormalizerV3Test.php
+++ b/tests/Swagger/Serializer/DocumentationNormalizerV3Test.php
@@ -111,8 +111,8 @@ private function doTestNormalize(OperationMethodResolverInterface $operationMeth
$resourceMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata);
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
- $propertyMetadataFactoryProphecy->create(Dummy::class, 'id')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false, null, null, null, true));
- $propertyMetadataFactoryProphecy->create(Dummy::class, 'name')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, []));
+ $propertyMetadataFactoryProphecy->create(Dummy::class, 'id')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_INT), 'This is an id.', true, false, null, null, null, true, null, null, null, null, null, null, null, ['minLength' => 3, 'maxLength' => 20, 'pattern' => '^dummyPattern$']));
+ $propertyMetadataFactoryProphecy->create(Dummy::class, 'name')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is a name.', true, true, true, true, false, false, null, null, [], null, null, null, null));
$propertyMetadataFactoryProphecy->create(Dummy::class, 'description')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), 'This is an initializable but not writable property.', true, false, true, true, false, false, null, null, [], null, true));
$propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyDate')->shouldBeCalled()->willReturn(new PropertyMetadata(new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTime::class), 'This is a \DateTimeInterface object.', true, true, true, true, false, false, null, null, []));
@@ -375,6 +375,9 @@ private function doTestNormalize(OperationMethodResolverInterface $operationMeth
'type' => 'integer',
'description' => 'This is an id.',
'readOnly' => true,
+ 'minLength' => 3,
+ 'maxLength' => 20,
+ 'pattern' => '^dummyPattern$',
]),
'name' => new \ArrayObject([
'type' => 'string',