From bef8b654ed410d16bf1086e2319ab215e5f36193 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Tue, 2 Jan 2024 15:56:33 +0100 Subject: [PATCH] Fix wrong detection of OA min/max for symfony contraints with datetime strings (#2148) * Fix symfony range and comparison constraints can compare datetime strings If the constraints contain a comparison value that is not int (e.g. datetime string), we cannot infer a OpenAPI value for it. This falsely casted those to int resulting in nonesense like minimum=0 and maximum=0 for string values. * add tests for comparison constraints on date --------- Co-authored-by: DjordyKoert --- .../SymfonyConstraintAnnotationReader.php | 28 +++++++---- .../Entity/SymfonyConstraints80.php | 49 +++++++++++++++++++ .../Entity/SymfonyConstraints81.php | 42 ++++++++++++++++ Tests/Functional/FunctionalTest.php | 36 ++++++++++++++ 4 files changed, 145 insertions(+), 10 deletions(-) diff --git a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php index 3a9b76d4b..b5eafbd1d 100644 --- a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php +++ b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php @@ -107,22 +107,30 @@ private function processPropertyAnnotations($reflection, OA\Property $property, } elseif ($annotation instanceof Assert\Choice) { $this->applyEnumFromChoiceConstraint($property, $annotation, $reflection); } elseif ($annotation instanceof Assert\Range) { - if (isset($annotation->min)) { - $property->minimum = (int) $annotation->min; + if (\is_int($annotation->min)) { + $property->minimum = $annotation->min; } - if (isset($annotation->max)) { - $property->maximum = (int) $annotation->max; + if (\is_int($annotation->max)) { + $property->maximum = $annotation->max; } } elseif ($annotation instanceof Assert\LessThan) { - $property->exclusiveMaximum = true; - $property->maximum = (int) $annotation->value; + if (\is_int($annotation->value)) { + $property->exclusiveMaximum = true; + $property->maximum = $annotation->value; + } } elseif ($annotation instanceof Assert\LessThanOrEqual) { - $property->maximum = (int) $annotation->value; + if (\is_int($annotation->value)) { + $property->maximum = $annotation->value; + } } elseif ($annotation instanceof Assert\GreaterThan) { - $property->exclusiveMinimum = true; - $property->minimum = (int) $annotation->value; + if (\is_int($annotation->value)) { + $property->exclusiveMinimum = true; + $property->minimum = $annotation->value; + } } elseif ($annotation instanceof Assert\GreaterThanOrEqual) { - $property->minimum = (int) $annotation->value; + if (\is_int($annotation->value)) { + $property->minimum = $annotation->value; + } } } } diff --git a/Tests/Functional/Entity/SymfonyConstraints80.php b/Tests/Functional/Entity/SymfonyConstraints80.php index 3274a50c3..a19184915 100644 --- a/Tests/Functional/Entity/SymfonyConstraints80.php +++ b/Tests/Functional/Entity/SymfonyConstraints80.php @@ -96,6 +96,13 @@ class SymfonyConstraints80 */ private $propertyRange; + /** + * @var \DateTimeImmutable + * + * @Assert\Range(min="now", max="+5 hours") + */ + public $propertyRangeDate; + /** * @var int * @@ -103,6 +110,13 @@ class SymfonyConstraints80 */ private $propertyLessThan; + /** + * @var \DateTimeImmutable + * + * @Assert\LessThan("now") + */ + public $propertyLessThanDate; + /** * @var int * @@ -110,6 +124,41 @@ class SymfonyConstraints80 */ private $propertyLessThanOrEqual; + /** + * @var \DateTimeImmutable + * + * @Assert\LessThanOrEqual("now") + */ + public $propertyLessThanOrEqualDate; + + /** + * @var int + * + * @Assert\GreaterThan(42) + */ + public $propertyGreaterThan; + + /** + * @var \DateTimeImmutable + * + * @Assert\GreaterThan("now") + */ + public $propertyGreaterThanDate; + + /** + * @var int + * + * @Assert\GreaterThanOrEqual(23) + */ + public $propertyGreaterThanOrEqual; + + /** + * @var \DateTimeImmutable + * + * @Assert\GreaterThanOrEqual("now") + */ + public $propertyGreaterThanOrEqualDate; + /** * @var int * diff --git a/Tests/Functional/Entity/SymfonyConstraints81.php b/Tests/Functional/Entity/SymfonyConstraints81.php index 7ab8bb9d5..4287b49a6 100644 --- a/Tests/Functional/Entity/SymfonyConstraints81.php +++ b/Tests/Functional/Entity/SymfonyConstraints81.php @@ -85,18 +85,60 @@ class SymfonyConstraints81 #[Assert\Range(min: 1, max: 5)] private $propertyRange; + /** + * @var \DateTimeImmutable + */ + #[Assert\Range(min: 'now', max: '+5 hours')] + public $propertyRangeDate; + /** * @var int */ #[Assert\LessThan(42)] private $propertyLessThan; + /** + * @var \DateTimeImmutable + */ + #[Assert\LessThan('now')] + public $propertyLessThanDate; + /** * @var int */ #[Assert\LessThanOrEqual(23)] private $propertyLessThanOrEqual; + /** + * @var \DateTimeImmutable + */ + #[Assert\LessThanOrEqual('now')] + public $propertyLessThanOrEqualDate; + + /** + * @var int + */ + #[Assert\GreaterThan(42)] + public $propertyGreaterThan; + + /** + * @var \DateTimeImmutable + */ + #[Assert\GreaterThan('now')] + public $propertyGreaterThanDate; + + /** + * @var int + */ + #[Assert\GreaterThanOrEqual(23)] + public $propertyGreaterThanOrEqual; + + /** + * @var \DateTimeImmutable + */ + #[Assert\GreaterThanOrEqual('now')] + public $propertyGreaterThanOrEqualDate; + /** * @var int */ diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php index a3b7dc667..452697b90 100644 --- a/Tests/Functional/FunctionalTest.php +++ b/Tests/Functional/FunctionalTest.php @@ -457,8 +457,15 @@ public function testSymfonyConstraintDocumentation() 'propertyChoiceWithMultiple', 'propertyExpression', 'propertyRange', + 'propertyRangeDate', 'propertyLessThan', + 'propertyLessThanDate', 'propertyLessThanOrEqual', + 'propertyLessThanOrEqualDate', + 'propertyGreaterThan', + 'propertyGreaterThanDate', + 'propertyGreaterThanOrEqual', + 'propertyGreaterThanOrEqualDate', ], 'properties' => [ 'propertyNotBlank' => [ @@ -510,18 +517,47 @@ public function testSymfonyConstraintDocumentation() 'maximum' => 5, 'minimum' => 1, ], + 'propertyRangeDate' => [ + 'type' => 'string', + 'format' => 'date-time', + ], 'propertyLessThan' => [ 'type' => 'integer', 'exclusiveMaximum' => true, 'maximum' => 42, ], + 'propertyLessThanDate' => [ + 'type' => 'string', + 'format' => 'date-time', + ], 'propertyLessThanOrEqual' => [ 'type' => 'integer', 'maximum' => 23, ], + 'propertyLessThanOrEqualDate' => [ + 'type' => 'string', + 'format' => 'date-time', + ], 'propertyWithCompoundValidationRule' => [ 'type' => 'integer', ], + 'propertyGreaterThan' => [ + 'type' => 'integer', + 'exclusiveMinimum' => true, + 'minimum' => 42, + ], + 'propertyGreaterThanDate' => [ + 'type' => 'string', + 'format' => 'date-time', + ], + 'propertyGreaterThanOrEqual' => [ + 'type' => 'integer', + 'minimum' => 23, + ], + 'propertyGreaterThanOrEqualDate' => [ + 'type' => 'string', + 'format' => 'date-time', + ], ], 'type' => 'object', 'schema' => $modelName,