Compound validation rule support (#1818)

* Compound validation rule support

* Compound validation rule support

* Compound validation rule support

* Compound validation rule support

* Remove duplicated method

* error during merge

* wrong variable name

* Simplify PR

* Fix CS

* Use same indentation as before

Co-authored-by: Guilhem Niot <guilhem@gniot.fr>
Co-authored-by: Guilhem Niot <guilhem.niot@gmail.com>
This commit is contained in:
Alexander Melihov 2021-06-16 10:59:06 +03:00 committed by GitHub
parent 187f55941c
commit 2df454c0c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 139 additions and 7 deletions

View File

@ -44,22 +44,33 @@ class SymfonyConstraintAnnotationReader
*/ */
public function updateProperty($reflection, OA\Property $property): void public function updateProperty($reflection, OA\Property $property): void
{ {
foreach ($this->getAnnotations($reflection) as $annotation) { foreach ($this->getAnnotations($reflection) as $outerAnnotation) {
$innerAnnotations = $outerAnnotation instanceof Assert\Compound
? $outerAnnotation->constraints
: [$outerAnnotation];
$this->processPropertyAnnotations($reflection, $property, $innerAnnotations);
}
}
private function processPropertyAnnotations($reflection, OA\Property $property, $annotations)
{
foreach ($annotations as $annotation) {
if ($annotation instanceof Assert\NotBlank || $annotation instanceof Assert\NotNull) { if ($annotation instanceof Assert\NotBlank || $annotation instanceof Assert\NotNull) {
// To support symfony/validator < 4.3 // To support symfony/validator < 4.3
if ($annotation instanceof Assert\NotBlank && \property_exists($annotation, 'allowNull') && $annotation->allowNull) { if ($annotation instanceof Assert\NotBlank && \property_exists($annotation, 'allowNull') && $annotation->allowNull) {
// The field is optional // The field is optional
continue; return;
} }
// The field is required // The field is required
if (null === $this->schema) { if (null === $this->schema) {
continue; return;
} }
$propertyName = $this->getSchemaPropertyName($property); $propertyName = $this->getSchemaPropertyName($property);
if (null === $propertyName) { if (null === $propertyName) {
continue; return;
} }
$existingRequiredFields = OA\UNDEFINED !== $this->schema->required ? $this->schema->required : []; $existingRequiredFields = OA\UNDEFINED !== $this->schema->required ? $this->schema->required : [];

View File

@ -11,6 +11,7 @@
namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;
use Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations\Fixture as CustomAssert;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
class SymfonyConstraints class SymfonyConstraints
@ -109,6 +110,18 @@ class SymfonyConstraints
*/ */
private $propertyLessThanOrEqual; private $propertyLessThanOrEqual;
/**
* @var int
*
* @CustomAssert\CompoundValidationRule()
*/
private $propertyWithCompoundValidationRule;
public function setPropertyWithCompoundValidationRule(int $propertyWithCompoundValidationRule): void
{
$this->propertyWithCompoundValidationRule = $propertyWithCompoundValidationRule;
}
/** /**
* @Assert\Count(min="0", max="10") * @Assert\Count(min="0", max="10")
*/ */

View File

@ -12,6 +12,7 @@
namespace Nelmio\ApiDocBundle\Tests\Functional; namespace Nelmio\ApiDocBundle\Tests\Functional;
use Nelmio\ApiDocBundle\OpenApiPhp\Util; use Nelmio\ApiDocBundle\OpenApiPhp\Util;
use Nelmio\ApiDocBundle\Tests\Helper;
use OpenApi\Annotations as OA; use OpenApi\Annotations as OA;
use Symfony\Component\Serializer\Annotation\SerializedName; use Symfony\Component\Serializer\Annotation\SerializedName;
@ -355,7 +356,7 @@ class FunctionalTest extends WebTestCase
public function testSymfonyConstraintDocumentation() public function testSymfonyConstraintDocumentation()
{ {
$this->assertEquals([ $expected = [
'required' => [ 'required' => [
'propertyNotBlank', 'propertyNotBlank',
'propertyNotNull', 'propertyNotNull',
@ -419,10 +420,26 @@ class FunctionalTest extends WebTestCase
'type' => 'integer', 'type' => 'integer',
'maximum' => 23, 'maximum' => 23,
], ],
'propertyWithCompoundValidationRule' => [
'type' => 'integer',
],
], ],
'type' => 'object', 'type' => 'object',
'schema' => 'SymfonyConstraints', 'schema' => 'SymfonyConstraints',
], json_decode($this->getModel('SymfonyConstraints')->toJson(), true)); ];
if (Helper::isCompoundValidatorConstraintSupported()) {
$expected['required'][] = 'propertyWithCompoundValidationRule';
$expected['properties']['propertyWithCompoundValidationRule'] = [
'type' => 'integer',
'maximum' => 5,
'exclusiveMaximum' => true,
'minimum' => 0,
'exclusiveMinimum' => true,
];
}
$this->assertEquals($expected, json_decode($this->getModel('SymfonyConstraints')->toJson(), true));
} }
public function testConfigReference() public function testConfigReference()

17
Tests/Helper.php Normal file
View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Nelmio\ApiDocBundle\Tests;
use PackageVersions\Versions;
final class Helper
{
public static function isCompoundValidatorConstraintSupported(): bool
{
$validatorVersion = Versions::getVersion('symfony/validator');
return version_compare($validatorVersion, 'v5.1', '>=');
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations\Fixture;
class CompoundStub
{
public $constraints = [];
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations\Fixture;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Constraints\Compound;
if (!class_exists(Compound::class)) {
class_alias(CompoundStub::class, Compound::class);
}
/**
* @Annotation
*/
final class CompoundValidationRule extends Compound
{
protected function getConstraints(array $options): array
{
return [
new Assert\Type('numeric'),
new Assert\NotBlank(),
new Assert\Positive(),
new Assert\LessThan(5),
];
}
}

View File

@ -13,6 +13,8 @@ namespace Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations;
use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationReader;
use Nelmio\ApiDocBundle\ModelDescriber\Annotations\SymfonyConstraintAnnotationReader; use Nelmio\ApiDocBundle\ModelDescriber\Annotations\SymfonyConstraintAnnotationReader;
use Nelmio\ApiDocBundle\Tests\Helper;
use Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations\Fixture as CustomAssert;
use OpenApi\Annotations as OA; use OpenApi\Annotations as OA;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@ -245,6 +247,39 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase
} }
} }
public function testCompoundValidationRules()
{
$entity = new class() {
/**
* @CustomAssert\CompoundValidationRule()
*/
private $property1;
};
$propertyName = 'property1';
$schema = new OA\Schema([]);
$schema->merge([new OA\Property(['property' => $propertyName])]);
$symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(new AnnotationReader());
$symfonyConstraintAnnotationReader->setSchema($schema);
$symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, $propertyName), $schema->properties[0]);
if (Helper::isCompoundValidatorConstraintSupported()) {
$this->assertSame([$propertyName], $schema->required);
$this->assertSame(0, $schema->properties[0]->minimum);
$this->assertTrue($schema->properties[0]->exclusiveMinimum);
$this->assertSame(5, $schema->properties[0]->maximum);
$this->assertTrue($schema->properties[0]->exclusiveMaximum);
} else {
$this->assertSame(OA\UNDEFINED, $schema->required);
$this->assertSame(OA\UNDEFINED, $schema->properties[0]->minimum);
$this->assertSame(OA\UNDEFINED, $schema->properties[0]->exclusiveMinimum);
$this->assertSame(OA\UNDEFINED, $schema->properties[0]->maximum);
$this->assertSame(OA\UNDEFINED, $schema->properties[0]->exclusiveMaximum);
}
}
/** /**
* @param object $entity * @param object $entity
* @group https://github.com/nelmio/NelmioApiDocBundle/issues/1821 * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1821

View File

@ -52,7 +52,8 @@
"friendsofsymfony/rest-bundle": "^2.8|^3.0", "friendsofsymfony/rest-bundle": "^2.8|^3.0",
"willdurand/hateoas-bundle": "^1.0|^2.0", "willdurand/hateoas-bundle": "^1.0|^2.0",
"jms/serializer-bundle": "^2.3|^3.0", "jms/serializer-bundle": "^2.3|^3.0",
"jms/serializer": "^1.14|^3.0" "jms/serializer": "^1.14|^3.0",
"composer/package-versions-deprecated": "1.11.99.1"
}, },
"suggest": { "suggest": {
"api-platform/core": "For using an API oriented framework.", "api-platform/core": "For using an API oriented framework.",