Skip to content

Commit cd5a278

Browse files
committed
Add exclusive minimum and exclusive maximum validators
Fix issue with mixed int/float multipleOf
1 parent e558599 commit cd5a278

File tree

7 files changed

+138
-45
lines changed

7 files changed

+138
-45
lines changed

src/PropertyProcessor/Property/AbstractNumericProcessor.php

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@
1111
* Class AbstractNumericProcessor
1212
*
1313
* @package PHPModelGenerator\PropertyProcessor\Property
14-
*
15-
* TODO: exclusiveMinimum, exclkusiveMaximum validator
1614
*/
1715
abstract class AbstractNumericProcessor extends AbstractTypedValueProcessor
1816
{
19-
protected const LIMIT_MESSAGE = 'Value for %s must not be %s than ';
17+
protected const LIMIT_MESSAGE = 'Value for %s must %s than %s';
2018

2119
protected const JSON_FIELD_MINIMUM = 'minimum';
2220
protected const JSON_FIELD_MAXIMUM = 'maximum';
2321

22+
protected const JSON_FIELD_MINIMUM_EXCLUSIVE = 'exclusiveMinimum';
23+
protected const JSON_FIELD_MAXIMUM_EXCLUSIVE = 'exclusiveMaximum';
24+
2425
protected const JSON_FIELD_MULTIPLE = 'multipleOf';
2526

2627
/**
@@ -30,48 +31,39 @@ protected function generateValidators(PropertyInterface $property, array $proper
3031
{
3132
parent::generateValidators($property, $propertyData);
3233

33-
$this->addMinimumValidator($property, $propertyData);
34-
$this->addMaximumValidator($property, $propertyData);
35-
$this->addMultipleOfValidator($property, $propertyData);
36-
}
34+
$this->addRangeValidator($property, $propertyData, self::JSON_FIELD_MINIMUM, '<', 'not be smaller');
35+
$this->addRangeValidator($property, $propertyData, self::JSON_FIELD_MAXIMUM, '>', 'not be larger');
3736

38-
/**
39-
* Adds a minimum validator to the property
40-
*
41-
* @param PropertyInterface $property
42-
* @param array $propertyData
43-
*/
44-
protected function addMinimumValidator(PropertyInterface $property, array $propertyData)
45-
{
46-
if (!isset($propertyData[self::JSON_FIELD_MINIMUM])) {
47-
return;
48-
}
37+
$this->addRangeValidator($property, $propertyData, self::JSON_FIELD_MINIMUM_EXCLUSIVE, '<=', 'be larger');
38+
$this->addRangeValidator($property, $propertyData, self::JSON_FIELD_MAXIMUM_EXCLUSIVE, '>=', 'be smaller');
4939

50-
$property->addValidator(
51-
new PropertyValidator(
52-
$this->getTypeCheck() . "\$value < {$propertyData[self::JSON_FIELD_MINIMUM]}",
53-
sprintf(static::LIMIT_MESSAGE, $property->getName(), 'smaller') .
54-
$propertyData[self::JSON_FIELD_MINIMUM]
55-
)
56-
);
40+
$this->addMultipleOfValidator($property, $propertyData);
5741
}
5842

5943
/**
60-
* Adds a maximum validator to the property
44+
* Adds a range validator to the property
6145
*
62-
* @param PropertyInterface $property
63-
* @param array $propertyData
46+
* @param PropertyInterface $property The property which shall be validated
47+
* @param array $propertyData The data for the property
48+
* @param string $field Which field of the property data provides the validation value
49+
* @param string $check The check to execute (eg. '<', '>')
50+
* @param string $message Message modifier
6451
*/
65-
protected function addMaximumValidator(PropertyInterface $property, array $propertyData)
66-
{
67-
if (!isset($propertyData[self::JSON_FIELD_MAXIMUM])) {
52+
protected function addRangeValidator(
53+
PropertyInterface $property,
54+
array $propertyData,
55+
string $field,
56+
string $check,
57+
string $message
58+
): void {
59+
if (!isset($propertyData[$field])) {
6860
return;
6961
}
7062

7163
$property->addValidator(
7264
new PropertyValidator(
73-
$this->getTypeCheck() . "\$value > {$propertyData[self::JSON_FIELD_MAXIMUM]}",
74-
sprintf(static::LIMIT_MESSAGE, $property->getName(), 'larger') . $propertyData[self::JSON_FIELD_MAXIMUM]
65+
$this->getTypeCheck() . "\$value $check {$propertyData[$field]}",
66+
sprintf(static::LIMIT_MESSAGE, $property->getName(), $message, $propertyData[$field])
7567
)
7668
);
7769
}
@@ -94,7 +86,7 @@ protected function addMultipleOfValidator(PropertyInterface $property, array $pr
9486
$propertyData[self::JSON_FIELD_MULTIPLE] == 0
9587
? $this->getTypeCheck() . '$value != 0'
9688
: (
97-
is_int($propertyData[self::JSON_FIELD_MULTIPLE])
89+
static::TYPE === 'int'
9890
? $this->getTypeCheck() . "\$value % {$propertyData[self::JSON_FIELD_MULTIPLE]} != 0"
9991
: $this->getTypeCheck() . "fmod(\$value, {$propertyData[self::JSON_FIELD_MULTIPLE]}) != 0"
10092
),

tests/AbstractPHPModelGeneratorTest.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,13 @@ protected function expectValidationError(GeneratorConfiguration $configuration,
252252
}
253253
}
254254

255-
protected function expectValidationErrorRegExp(GeneratorConfiguration $configuration, $messages)
255+
/**
256+
* Expect a validation error based on the given configuration matching the given message(s)
257+
*
258+
* @param GeneratorConfiguration $configuration
259+
* @param array|string $messages
260+
*/
261+
protected function expectValidationErrorRegExp(GeneratorConfiguration $configuration, $messages): void
256262
{
257263
if (!is_array($messages)) {
258264
$messages = [$messages];
@@ -338,7 +344,7 @@ protected function getMethodReturnType(object $object, string $method): string
338344
*
339345
* @return string
340346
*/
341-
private function getClassName()
347+
private function getClassName(): string
342348
{
343349
// include the static class name to avoid collisions from loaded classes from multiple tests
344350
$name = $this->getStaticClassName() . '_' . uniqid();
@@ -352,7 +358,7 @@ private function getClassName()
352358
return $name;
353359
}
354360

355-
private function getStaticClassName()
361+
private function getStaticClassName(): string
356362
{
357363
$parts = explode('\\', static::class);
358364

tests/Objects/AbstractNumericPropertyTest.php

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@
1212
*/
1313
abstract class AbstractNumericPropertyTest extends AbstractPHPModelGeneratorTest
1414
{
15-
abstract protected function getRangeFile(): string;
15+
abstract protected function getRangeFile(bool $exclusive): string;
1616

1717
abstract protected function getMultipleOfFile(): string;
1818

1919
abstract public function validRangeDataProvider(): iterable;
2020

2121
abstract public function invalidRangeDataProvider(): iterable;
2222

23+
abstract public function validExclusiveRangeDataProvider(): iterable;
24+
25+
abstract public function invalidExclusiveRangeDataProvider(): iterable;
26+
2327
abstract public function validMultipleOfDataProvider(): iterable;
2428

2529
abstract public function invalidMultipleOfDataProvider(): iterable;
@@ -31,7 +35,19 @@ abstract public function invalidMultipleOfDataProvider(): iterable;
3135
*/
3236
public function testValidValueForRangeValidator($propertyValue)
3337
{
34-
$className = $this->generateClassFromFile($this->getRangeFile());
38+
$className = $this->generateClassFromFile($this->getRangeFile(false));
39+
40+
$object = new $className(['property' => $propertyValue]);
41+
$this->assertEquals($propertyValue, $object->getProperty());
42+
}
43+
/**
44+
* @dataProvider validExclusiveRangeDataProvider
45+
*
46+
* @param $propertyValue
47+
*/
48+
public function testValidValueForExclusiveRangeValidator($propertyValue)
49+
{
50+
$className = $this->generateClassFromFile($this->getRangeFile(true));
3551

3652
$object = new $className(['property' => $propertyValue]);
3753
$this->assertEquals($propertyValue, $object->getProperty());
@@ -48,7 +64,23 @@ public function testInvalidValueForRangeValidatorThrowsAnException($propertyValu
4864
$this->expectException(ValidationException::class);
4965
$this->expectExceptionMessage($exceptionMessage);
5066

51-
$className = $this->generateClassFromFile($this->getRangeFile());
67+
$className = $this->generateClassFromFile($this->getRangeFile(false));
68+
69+
new $className(['property' => $propertyValue]);
70+
}
71+
72+
/**
73+
* @dataProvider invalidExclusiveRangeDataProvider
74+
*
75+
* @param $propertyValue
76+
* @param string $exceptionMessage
77+
*/
78+
public function testInvalidValueForExclusiveRangeValidatorThrowsAnException($propertyValue, string $exceptionMessage)
79+
{
80+
$this->expectException(ValidationException::class);
81+
$this->expectExceptionMessage($exceptionMessage);
82+
83+
$className = $this->generateClassFromFile($this->getRangeFile(true));
5284

5385
new $className(['property' => $propertyValue]);
5486
}

tests/Objects/IntegerPropertyTest.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ public function invalidMultipleOfDataProvider(): iterable
114114
];
115115
}
116116

117-
protected function getRangeFile(): string
117+
protected function getRangeFile(bool $exclusive): string
118118
{
119-
return 'IntegerPropertyRange.json';
119+
return $exclusive ? 'IntegerPropertyRangeExclusive.json' : 'IntegerPropertyRange.json';
120120
}
121121

122122
public function validRangeDataProvider(): iterable
@@ -136,4 +136,22 @@ public function invalidRangeDataProvider(): iterable
136136
'Too small number' => [-2, 'Value for property must not be smaller than -1'],
137137
];
138138
}
139+
140+
public function validExclusiveRangeDataProvider(): iterable
141+
{
142+
return [
143+
'Zero' => [0],
144+
'Null' => [null],
145+
];
146+
}
147+
148+
public function invalidExclusiveRangeDataProvider(): iterable
149+
{
150+
return [
151+
'Too large number 1' => [2, 'Value for property must be smaller than 1'],
152+
'Too large number 2' => [1, 'Value for property must be smaller than 1'],
153+
'Too small number 1' => [-1, 'Value for property must be larger than -1'],
154+
'Too small number 2' => [-2, 'Value for property must be larger than -1'],
155+
];
156+
}
139157
}

tests/Objects/NumberPropertyTest.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,14 @@ public function invalidMultipleOfDataProvider(): iterable
144144
'0.1 is multiple of 0.0' => [.0, .1],
145145
'-0.1 is multiple of 0.0' => [.0, -.1],
146146
// check mixed multiples
147-
'4 is multiple of 1.5' => [1.5, 4]
147+
'4 is multiple of 1.5' => [1.5, 4],
148+
'2.7 is multiple of 2' => [2, 2.7],
148149
];
149150
}
150151

151-
protected function getRangeFile(): string
152+
protected function getRangeFile(bool $exclusive): string
152153
{
153-
return 'NumberPropertyRange.json';
154+
return $exclusive ? 'NumberPropertyRangeExclusive.json' : 'NumberPropertyRange.json';
154155
}
155156

156157
public function validRangeDataProvider(): iterable
@@ -175,4 +176,28 @@ public function invalidRangeDataProvider(): iterable
175176
'Too small number float' => [-1.7, 'Value for property must not be smaller than -1.6'],
176177
];
177178
}
179+
public function validExclusiveRangeDataProvider(): iterable
180+
{
181+
return [
182+
'Upper limit float' => [1.59],
183+
'Upper limit int' => [1],
184+
'Zero' => [0],
185+
'Zero float' => [.0],
186+
'Lower limit float' => [-1.59],
187+
'Lower limit int' => [-1],
188+
'Null' => [null],
189+
];
190+
}
191+
192+
public function invalidExclusiveRangeDataProvider(): iterable
193+
{
194+
return [
195+
'Too large number int' => [2, 'Value for property must be smaller than 1.6'],
196+
'Too large number float 1' => [1.6, 'Value for property must be smaller than 1.6'],
197+
'Too large number float 2' => [1.9, 'Value for property must be smaller than 1.6'],
198+
'Too small number int' => [-2, 'Value for property must be larger than -1.6'],
199+
'Too small number float 1' => [-1.6, 'Value for property must be larger than -1.6'],
200+
'Too small number float 2' => [-1.9, 'Value for property must be larger than -1.6'],
201+
];
202+
}
178203
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"property": {
5+
"type": "integer",
6+
"exclusiveMinimum": -1,
7+
"exclusiveMaximum": 1
8+
}
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"property": {
5+
"type": "number",
6+
"exclusiveMinimum": -1.6,
7+
"exclusiveMaximum": 1.6
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)