Skip to content

Commit d160545

Browse files
committed
Add additional test cases for conditional compositions
Fix nested compositions
1 parent 466523c commit d160545

File tree

5 files changed

+181
-24
lines changed

5 files changed

+181
-24
lines changed

src/Model/Validator/ComposedPropertyValidator.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPModelGenerator\Exception\ComposedValue\InvalidComposedValueException;
88
use PHPModelGenerator\Model\Property\CompositionPropertyDecorator;
99
use PHPModelGenerator\Model\Property\PropertyInterface;
10+
use PHPModelGenerator\Model\Validator;
1011

1112
/**
1213
* Class ComposedPropertyValidator
@@ -54,6 +55,25 @@ public function getValidatorSetUp(): string
5455
';
5556
}
5657

58+
/**
59+
* Creates a copy of the validator and strips all nested composition validations from the composed properties.
60+
* See usage in BaseProcessor for more details why the nested validators can be filtered out.
61+
*
62+
* @return $this
63+
*/
64+
public function withoutNestedCompositionValidation(): self
65+
{
66+
$validator = clone $this;
67+
68+
foreach ($validator->composedProperties as $composedProperty) {
69+
$composedProperty->filterValidators(function (Validator $validator): bool {
70+
return !is_a($validator->getValidator(), AbstractComposedPropertyValidator::class);
71+
});
72+
}
73+
74+
return $validator;
75+
}
76+
5777
/**
5878
* Parse the composition type (allOf, anyOf, ...) from the given processor and get the corresponding exception class
5979
*

src/PropertyProcessor/Property/BaseProcessor.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use PHPModelGenerator\Model\Validator;
1919
use PHPModelGenerator\Model\Validator\AbstractComposedPropertyValidator;
2020
use PHPModelGenerator\Model\Validator\AdditionalPropertiesValidator;
21+
use PHPModelGenerator\Model\Validator\ComposedPropertyValidator;
2122
use PHPModelGenerator\Model\Validator\PropertyNamesValidator;
2223
use PHPModelGenerator\Model\Validator\PropertyTemplateValidator;
2324
use PHPModelGenerator\Model\Validator\PropertyValidator;
@@ -253,8 +254,16 @@ protected function transferComposedPropertiesToSchema(PropertyInterface $propert
253254
if (!is_a($validator, AbstractComposedPropertyValidator::class)) {
254255
continue;
255256
}
256-
/** @var AbstractComposedPropertyValidator $validator */
257-
$this->schema->addBaseValidator($validator);
257+
258+
// If the transferred validator of the composed property is also a composed property strip the nested
259+
// composition validations from the added validator. The nested composition will be validated in the object
260+
// generated for the nested composition which will be executed via an instanciation. Consequently the
261+
// validation must not be executed in the outer composition.
262+
$this->schema->addBaseValidator(
263+
($validator instanceof ComposedPropertyValidator)
264+
? $validator->withoutNestedCompositionValidation()
265+
: $validator
266+
);
258267

259268
if (!is_a($validator->getComposedProcessor(), ComposedPropertiesInterface::class, true)) {
260269
continue;

tests/ComposedValue/ComposedIfTest.php

Lines changed: 90 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPModelGenerator\Tests\ComposedValue;
44

55
use PHPModelGenerator\Exception\FileSystemException;
6+
use PHPModelGenerator\Exception\Number\MultipleOfException;
67
use PHPModelGenerator\Exception\RenderException;
78
use PHPModelGenerator\Exception\SchemaException;
89
use PHPModelGenerator\Model\GeneratorConfiguration;
@@ -39,21 +40,69 @@ public function conditionalKeywordsDataProvider(): array
3940
];
4041
}
4142

43+
/**
44+
* @dataProvider validConditionalPropertyDefinitionDataProvider
45+
* @param int $value
46+
*/
47+
public function testConditionalPropertyDefinition(int $value): void
48+
{
49+
$className = $this->generateClassFromFile('ConditionalPropertyDefinition.json');
50+
51+
$object = new $className(['property' => $value]);
52+
$this->assertSame($value, $object->getProperty());
53+
}
54+
55+
public function validConditionalPropertyDefinitionDataProvider(): array
56+
{
57+
return [
58+
'zero' => [0],
59+
'negative multiple of else' => [-30],
60+
'positive multiple of else 1' => [30],
61+
'positive multiple of else 2' => [60],
62+
'exactly on minimum' => [100],
63+
'positive multiple of then' => [150],
64+
];
65+
}
66+
67+
/**
68+
* @dataProvider invalidConditionalPropertyDefinitionDataProvider
69+
* @param int $value
70+
*/
71+
public function testInvalidConditionalPropertyDefinition(int $value): void
72+
{
73+
$this->expectException(MultipleOfException::class);
74+
$this->expectExceptionMessage("Value for property must be a multiple of");
75+
76+
$className = $this->generateClassFromFile('ConditionalPropertyDefinition.json');
77+
new $className(['property' => $value]);
78+
}
79+
80+
public function invalidConditionalPropertyDefinitionDataProvider(): array
81+
{
82+
return [
83+
'invalid negative' => [-50],
84+
'invalid positive else' => [50],
85+
'invalid positive then' => [120],
86+
];
87+
}
88+
4289
/**
4390
* @dataProvider validConditionalObjectPropertyDataProvider
4491
*
92+
* @param string $schemaFile
4593
* @param GeneratorConfiguration $configuration
46-
* @param string $streetAddress
47-
* @param string $country
48-
* @param string $postalCode
94+
* @param string|null $streetAddress
95+
* @param string|null $country
96+
* @param string|null $postalCode
4997
*/
5098
public function testConditionalObjectProperty(
99+
string $schemaFile,
51100
GeneratorConfiguration $configuration,
52101
?string $streetAddress,
53102
?string $country,
54103
?string $postalCode
55104
): void {
56-
$className = $this->generateClassFromFile('ConditionalObjectProperty.json', $configuration);
105+
$className = $this->generateClassFromFile($schemaFile, $configuration);
57106

58107
$object = new $className([
59108
'street_address' => $streetAddress,
@@ -66,39 +115,55 @@ public function testConditionalObjectProperty(
66115
$this->assertSame($postalCode, $object->getPostalCode());
67116
}
68117

118+
public function objectLevelConditionalSchemaDataProvider(): array
119+
{
120+
return [
121+
'Object top level conditional composition' => ['ConditionalObjectProperty.json'],
122+
'Conditional composition nested in another composition' => ['NestedIfInComposition.json'],
123+
];
124+
}
125+
69126
public function validConditionalObjectPropertyDataProvider(): array
70127
{
71128
return $this->combineDataProvider(
72-
$this->validationMethodDataProvider(),
73-
[
74-
'not provided postal code' => ['1600 Pennsylvania Avenue NW', 'USA', null],
75-
'USA postal code' => ['1600 Pennsylvania Avenue NW', 'USA', '20500'],
76-
'Canada postal code' => ['24 Sussex Drive', 'Canada', 'K1M 1M4'],
77-
]
129+
$this->objectLevelConditionalSchemaDataProvider(),
130+
$this->combineDataProvider(
131+
$this->validationMethodDataProvider(),
132+
[
133+
'not provided postal code' => ['1600 Pennsylvania Avenue NW', 'USA', null],
134+
'USA postal code' => ['1600 Pennsylvania Avenue NW', 'USA', '20500'],
135+
'Canada postal code' => ['24 Sussex Drive', 'Canada', 'K1M 1M4'],
136+
]
137+
)
78138
);
79139
}
80140

81141
/**
82142
* @dataProvider invalidConditionalObjectPropertyDataProvider
83143
*
144+
* @param string $schemaFile
84145
* @param GeneratorConfiguration $configuration
85-
* @param string $streetAddress
86-
* @param string $country
87-
* @param string $postalCode
146+
* @param string|null $streetAddress
147+
* @param string|null $country
148+
* @param string|null $postalCode
88149
*
89150
* @throws FileSystemException
90151
* @throws RenderException
91152
* @throws SchemaException
92153
*/
93154
public function testInvalidConditionalObjectPropertyThrowsAnException(
155+
string $schemaFile,
94156
GeneratorConfiguration $configuration,
95157
?string $streetAddress,
96158
?string $country,
97159
?string $postalCode
98160
): void {
99-
$this->expectValidationErrorRegExp($configuration, '/postal_code doesn\'t match pattern .*/');
161+
$this->expectValidationErrorRegExp(
162+
$configuration,
163+
'/(Invalid value for .*? declined by composition constraint|postal_code doesn\'t match pattern .*)/'
164+
);
100165

101-
$className = $this->generateClassFromFile('ConditionalObjectProperty.json', $configuration);
166+
$className = $this->generateClassFromFile($schemaFile, $configuration);
102167

103168
new $className([
104169
'street_address' => $streetAddress,
@@ -110,13 +175,16 @@ public function testInvalidConditionalObjectPropertyThrowsAnException(
110175
public function invalidConditionalObjectPropertyDataProvider(): array
111176
{
112177
return $this->combineDataProvider(
113-
$this->validationMethodDataProvider(),
114-
[
115-
'empty provided postal code' => ['1600 Pennsylvania Avenue NW', 'USA', ''],
116-
'Canadian postal code for USA' => ['1600 Pennsylvania Avenue NW', 'USA', 'K1M 1M4'],
117-
'USA postal code for Canada' => ['24 Sussex Drive', 'Canada', '20500'],
118-
'Unmatching postal code for both' => ['24 Sussex Drive', 'Canada', 'djqwWDJId8juw9duq9'],
119-
]
178+
$this->objectLevelConditionalSchemaDataProvider(),
179+
$this->combineDataProvider(
180+
$this->validationMethodDataProvider(),
181+
[
182+
'empty provided postal code' => ['1600 Pennsylvania Avenue NW', 'USA', ''],
183+
'Canadian postal code for USA' => ['1600 Pennsylvania Avenue NW', 'USA', 'K1M 1M4'],
184+
'USA postal code for Canada' => ['24 Sussex Drive', 'Canada', '20500'],
185+
'Unmatching postal code for both' => ['24 Sussex Drive', 'Canada', 'djqwWDJId8juw9duq9'],
186+
]
187+
)
120188
);
121189
}
122190

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"property": {
5+
"type": "integer",
6+
"if": {
7+
"minimum": 100
8+
},
9+
"then": {
10+
"multipleOf": 50
11+
},
12+
"else": {
13+
"multipleOf": 30
14+
}
15+
}
16+
}
17+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"allOf": [
3+
{
4+
"type": "object",
5+
"properties": {
6+
"street_address": {
7+
"type": "string"
8+
},
9+
"country": {
10+
"enum": [
11+
"USA",
12+
"Canada"
13+
]
14+
}
15+
}
16+
},
17+
{
18+
"if": {
19+
"properties": {
20+
"country": {
21+
"const": "USA"
22+
}
23+
}
24+
},
25+
"then": {
26+
"properties": {
27+
"postal_code": {
28+
"type": "string",
29+
"pattern": "[0-9]{5}(-[0-9]{4})?"
30+
}
31+
}
32+
},
33+
"else": {
34+
"properties": {
35+
"postal_code": {
36+
"type": "string",
37+
"pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]"
38+
}
39+
}
40+
}
41+
}
42+
]
43+
}

0 commit comments

Comments
 (0)