diff --git a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php index 2b1733b..8fa31f6 100644 --- a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php +++ b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php @@ -44,22 +44,33 @@ class SymfonyConstraintAnnotationReader */ 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) { // To support symfony/validator < 4.3 if ($annotation instanceof Assert\NotBlank && \property_exists($annotation, 'allowNull') && $annotation->allowNull) { // The field is optional - continue; + return; } // The field is required if (null === $this->schema) { - continue; + return; } $propertyName = $this->getSchemaPropertyName($property); if (null === $propertyName) { - continue; + return; } $existingRequiredFields = OA\UNDEFINED !== $this->schema->required ? $this->schema->required : []; diff --git a/Tests/Functional/Entity/SymfonyConstraints.php b/Tests/Functional/Entity/SymfonyConstraints.php index 0cada3d..05e65c9 100644 --- a/Tests/Functional/Entity/SymfonyConstraints.php +++ b/Tests/Functional/Entity/SymfonyConstraints.php @@ -11,6 +11,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Entity; +use Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations\Fixture as CustomAssert; use Symfony\Component\Validator\Constraints as Assert; class SymfonyConstraints @@ -109,6 +110,18 @@ class SymfonyConstraints */ private $propertyLessThanOrEqual; + /** + * @var int + * + * @CustomAssert\CompoundValidationRule() + */ + private $propertyWithCompoundValidationRule; + + public function setPropertyWithCompoundValidationRule(int $propertyWithCompoundValidationRule): void + { + $this->propertyWithCompoundValidationRule = $propertyWithCompoundValidationRule; + } + /** * @Assert\Count(min="0", max="10") */ diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php index 64a194b..9570edf 100644 --- a/Tests/Functional/FunctionalTest.php +++ b/Tests/Functional/FunctionalTest.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use Nelmio\ApiDocBundle\OpenApiPhp\Util; +use Nelmio\ApiDocBundle\Tests\Helper; use OpenApi\Annotations as OA; use Symfony\Component\Serializer\Annotation\SerializedName; @@ -355,7 +356,7 @@ class FunctionalTest extends WebTestCase public function testSymfonyConstraintDocumentation() { - $this->assertEquals([ + $expected = [ 'required' => [ 'propertyNotBlank', 'propertyNotNull', @@ -419,10 +420,26 @@ class FunctionalTest extends WebTestCase 'type' => 'integer', 'maximum' => 23, ], + 'propertyWithCompoundValidationRule' => [ + 'type' => 'integer', + ], ], 'type' => 'object', '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() diff --git a/Tests/Helper.php b/Tests/Helper.php new file mode 100644 index 0000000..10f94d0 --- /dev/null +++ b/Tests/Helper.php @@ -0,0 +1,17 @@ +='); + } +} diff --git a/Tests/ModelDescriber/Annotations/Fixture/CompoundStub.php b/Tests/ModelDescriber/Annotations/Fixture/CompoundStub.php new file mode 100644 index 0000000..4167da0 --- /dev/null +++ b/Tests/ModelDescriber/Annotations/Fixture/CompoundStub.php @@ -0,0 +1,10 @@ +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 * @group https://github.com/nelmio/NelmioApiDocBundle/issues/1821 diff --git a/composer.json b/composer.json index 9a1a204..0c00d20 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,8 @@ "friendsofsymfony/rest-bundle": "^2.8|^3.0", "willdurand/hateoas-bundle": "^1.0|^2.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": { "api-platform/core": "For using an API oriented framework.",