Skip to content

Commit e8196ef

Browse files
fix: Count constraint uses minProperties/maxProperties for maps (#2688)
## Summary - When a schema has type `object` (e.g. `array<string, Y>` maps), the `Count` constraint now correctly sets `minProperties`/`maxProperties` instead of `minItems`/`maxItems`, per the OpenAPI specification - Schemas with type `array` continue using `minItems`/`maxItems` as before Fixes #2578 --------- Co-authored-by: djordy <djordy.koert@yoursurprise.com>
1 parent 3c06392 commit e8196ef

3 files changed

Lines changed: 50 additions & 45 deletions

File tree

src/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,20 @@ private function processPropertyAttributes($reflection, OA\Property $property, a
9797
} elseif ($attribute instanceof Assert\Regex) {
9898
$this->appendPattern($property, $attribute->getHtmlPattern());
9999
} elseif ($attribute instanceof Assert\Count) {
100-
if (isset($attribute->min)) {
101-
$property->minItems = $attribute->min;
102-
}
103-
if (isset($attribute->max)) {
104-
$property->maxItems = $attribute->max;
100+
if ('object' === $property->type) {
101+
if (isset($attribute->min)) {
102+
$property->minProperties = $attribute->min;
103+
}
104+
if (isset($attribute->max)) {
105+
$property->maxProperties = $attribute->max;
106+
}
107+
} else {
108+
if (isset($attribute->min)) {
109+
$property->minItems = $attribute->min;
110+
}
111+
if (isset($attribute->max)) {
112+
$property->maxItems = $attribute->max;
113+
}
105114
}
106115
} elseif ($attribute instanceof Assert\Choice) {
107116
$this->applyEnumFromChoiceConstraint($property, $attribute, $reflection);

tests/ModelDescriber/Annotations/AnnotationReaderTest.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,8 @@ class AnnotationReaderTest extends TestCase
2525
{
2626
use SetsContextTrait;
2727

28-
/**
29-
* @param object $entity
30-
*/
3128
#[DataProvider('provideProperty')]
32-
public function testProperty($entity): void
29+
public function testProperty(object $entity): void
3330
{
3431
$baseProps = ['_context' => new Context()];
3532

tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,8 @@ public function testUpdatePropertyFix1283(): void
4848
self::assertEquals($schema->required, ['property1', 'property2']);
4949
}
5050

51-
/**
52-
* @param object $entity
53-
*/
5451
#[DataProvider('provideOptionalProperty')]
55-
public function testOptionalProperty($entity): void
52+
public function testOptionalProperty(object $entity): void
5653
{
5754
$schema = $this->createObj(OA\Schema::class, []);
5855
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);
@@ -79,11 +76,8 @@ public static function provideOptionalProperty(): \Generator
7976
}];
8077
}
8178

82-
/**
83-
* @param object $entity
84-
*/
8579
#[DataProvider('provideAssertChoiceResultsInNumericArray')]
86-
public function testAssertChoiceResultsInNumericArray($entity): void
80+
public function testAssertChoiceResultsInNumericArray(object $entity): void
8781
{
8882
$schema = $this->createObj(OA\Schema::class, []);
8983
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);
@@ -111,11 +105,8 @@ public static function provideAssertChoiceResultsInNumericArray(): \Generator
111105
}];
112106
}
113107

114-
/**
115-
* @param object $entity
116-
*/
117108
#[DataProvider('provideMultipleChoiceConstraintsApplyEnumToItems')]
118-
public function testMultipleChoiceConstraintsApplyEnumToItems($entity): void
109+
public function testMultipleChoiceConstraintsApplyEnumToItems(object $entity): void
119110
{
120111
$schema = $this->createObj(OA\Schema::class, []);
121112
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);
@@ -137,11 +128,8 @@ public static function provideMultipleChoiceConstraintsApplyEnumToItems(): \Gene
137128
}];
138129
}
139130

140-
/**
141-
* @param object $entity
142-
*/
143131
#[DataProvider('provideLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet')]
144-
public function testLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet($entity): void
132+
public function testLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet(object $entity): void
145133
{
146134
$schema = $this->createObj(OA\Schema::class, []);
147135
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);
@@ -163,11 +151,8 @@ public static function provideLengthConstraintDoesNotSetMaxLengthIfMaxIsNotSet()
163151
}];
164152
}
165153

166-
/**
167-
* @param object $entity
168-
*/
169154
#[DataProvider('provideLengthConstraintDoesNotSetMinLengthIfMinIsNotSet')]
170-
public function testLengthConstraintDoesNotSetMinLengthIfMinIsNotSet($entity): void
155+
public function testLengthConstraintDoesNotSetMinLengthIfMinIsNotSet(object $entity): void
171156
{
172157
$schema = $this->createObj(OA\Schema::class, []);
173158
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);
@@ -212,11 +197,8 @@ public function testCompoundValidationRules(): void
212197
self::assertTrue($schema->properties[0]->exclusiveMaximum);
213198
}
214199

215-
/**
216-
* @param object $entity
217-
*/
218200
#[DataProvider('provideCountConstraintDoesNotSetMinItemsIfMinIsNotSet')]
219-
public function testCountConstraintDoesNotSetMinItemsIfMinIsNotSet($entity): void
201+
public function testCountConstraintDoesNotSetMinItemsIfMinIsNotSet(object $entity): void
220202
{
221203
$schema = $this->createObj(OA\Schema::class, []);
222204
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);
@@ -238,11 +220,8 @@ public static function provideCountConstraintDoesNotSetMinItemsIfMinIsNotSet():
238220
}];
239221
}
240222

241-
/**
242-
* @param object $entity
243-
*/
244223
#[DataProvider('provideCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet')]
245-
public function testCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet($entity): void
224+
public function testCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet(object $entity): void
246225
{
247226
$schema = $this->createObj(OA\Schema::class, []);
248227
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);
@@ -264,11 +243,34 @@ public static function provideCountConstraintDoesNotSetMaxItemsIfMaxIsNotSet():
264243
}];
265244
}
266245

267-
/**
268-
* @param object $entity
269-
*/
246+
#[DataProvider('provideCountConstraintSetsMinMaxPropertiesForObjectType')]
247+
public function testCountConstraintSetsMinMaxPropertiesForObjectType(object $entity): void
248+
{
249+
$schema = $this->createObj(OA\Schema::class, []);
250+
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);
251+
$schema->properties[0]->type = 'object';
252+
253+
$symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader();
254+
$symfonyConstraintAnnotationReader->setSchema($schema);
255+
256+
$symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]);
257+
258+
self::assertSame(1, $schema->properties[0]->minProperties);
259+
self::assertSame(10, $schema->properties[0]->maxProperties);
260+
self::assertSame(Generator::UNDEFINED, $schema->properties[0]->minItems);
261+
self::assertSame(Generator::UNDEFINED, $schema->properties[0]->maxItems);
262+
}
263+
264+
public static function provideCountConstraintSetsMinMaxPropertiesForObjectType(): \Generator
265+
{
266+
yield 'Attributes' => [new class {
267+
#[Assert\Count(min: 1, max: 10)]
268+
public $property1;
269+
}];
270+
}
271+
270272
#[DataProvider('provideRangeConstraintDoesNotSetMaximumIfMaxIsNotSet')]
271-
public function testRangeConstraintDoesNotSetMaximumIfMaxIsNotSet($entity): void
273+
public function testRangeConstraintDoesNotSetMaximumIfMaxIsNotSet(object $entity): void
272274
{
273275
$schema = $this->createObj(OA\Schema::class, []);
274276
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);
@@ -290,11 +292,8 @@ public static function provideRangeConstraintDoesNotSetMaximumIfMaxIsNotSet(): \
290292
}];
291293
}
292294

293-
/**
294-
* @param object $entity
295-
*/
296295
#[DataProvider('provideRangeConstraintDoesNotSetMinimumIfMinIsNotSet')]
297-
public function testRangeConstraintDoesNotSetMinimumIfMinIsNotSet($entity): void
296+
public function testRangeConstraintDoesNotSetMinimumIfMinIsNotSet(object $entity): void
298297
{
299298
$schema = $this->createObj(OA\Schema::class, []);
300299
$schema->merge([$this->createObj(OA\Property::class, ['property' => 'property1'])]);

0 commit comments

Comments
 (0)