Skip to content

Commit 8bab93f

Browse files
committed
Added propertyNames validation
Improved multiline exception formatting
1 parent cd5a278 commit 8bab93f

File tree

10 files changed

+408
-31
lines changed

10 files changed

+408
-31
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,13 @@ $person->setAge(-10);
143143
More complex exception messages eg. from a [allOf](https://json-schema.org/understanding-json-schema/reference/combining.html#allof) composition may look like:
144144
```
145145
Invalid value for Animal declined by composition constraint.
146-
Requires to match 3 composition elements but matched 1 elements.
147-
- Composition element #1: Failed
148-
* Value for age must not be smaller than 0
149-
- Composition element #2: Valid
150-
- Composition element #3: Failed
151-
* Value for legs must not be smaller than 2
152-
* Value for legs must be a multiple of 2
146+
Requires to match 3 composition elements but matched 1 elements.
147+
- Composition element #1: Failed
148+
* Value for age must not be smaller than 0
149+
- Composition element #2: Valid
150+
- Composition element #3: Failed
151+
* Value for legs must not be smaller than 2
152+
* Value for legs must be a multiple of 2
153153
```
154154

155155
## How the heck does this work? ##
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\Model\Validator;
6+
7+
use PHPMicroTemplate\Exception\FileSystemException;
8+
use PHPMicroTemplate\Exception\SyntaxErrorException;
9+
use PHPMicroTemplate\Exception\UndefinedSymbolException;
10+
use PHPModelGenerator\Exception\SchemaException;
11+
use PHPModelGenerator\Model\Schema;
12+
use PHPModelGenerator\Model\Validator;
13+
use PHPModelGenerator\PropertyProcessor\Property\StringProcessor;
14+
use PHPModelGenerator\PropertyProcessor\PropertyCollectionProcessor;
15+
use PHPModelGenerator\SchemaProcessor\SchemaProcessor;
16+
use PHPModelGenerator\Utils\RenderHelper;
17+
18+
/**
19+
* Class PropertyNamesValidator
20+
*
21+
* @package PHPModelGenerator\Model\Validator
22+
*/
23+
class PropertyNamesValidator extends AbstractComposedPropertyValidator
24+
{
25+
/**
26+
* ComposedPropertyValidator constructor.
27+
*
28+
* @param PropertyCollectionProcessor $propertyCollectionProcessor
29+
* @param SchemaProcessor $schemaProcessor
30+
* @param Schema $schema
31+
* @param array $propertyNames
32+
*
33+
* @throws FileSystemException
34+
* @throws SyntaxErrorException
35+
* @throws UndefinedSymbolException
36+
* @throws SchemaException
37+
*/
38+
public function __construct(
39+
PropertyCollectionProcessor $propertyCollectionProcessor,
40+
SchemaProcessor $schemaProcessor,
41+
Schema $schema,
42+
array $propertyNames
43+
) {
44+
$nameValidationProperty = (new StringProcessor($propertyCollectionProcessor, $schemaProcessor, $schema))
45+
->process('property name', $propertyNames)
46+
// the property name validator doesn't need type checks or required checks so simply filter them out
47+
->filterValidators(function (Validator $validator) {
48+
return !is_a($validator->getValidator(), RequiredPropertyValidator::class) &&
49+
!is_a($validator->getValidator(), TypeCheckValidator::class);
50+
});
51+
52+
parent::__construct(
53+
$this->getRenderer()->renderTemplate(
54+
DIRECTORY_SEPARATOR . 'Exception' . DIRECTORY_SEPARATOR . 'PropertyNamesException.phptpl'
55+
),
56+
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'PropertyNames.phptpl',
57+
[
58+
'nameValidationProperty' => $nameValidationProperty,
59+
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),
60+
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
61+
]
62+
);
63+
}
64+
65+
/**
66+
* Initialize all variables which are required to execute a property names validator
67+
*
68+
* @return string
69+
*/
70+
public function getValidatorSetUp(): string
71+
{
72+
return '$invalidProperties = [];';
73+
}
74+
}

src/PropertyProcessor/Property/BaseProcessor.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44

55
namespace PHPModelGenerator\PropertyProcessor\Property;
66

7+
use PHPMicroTemplate\Exception\FileSystemException;
8+
use PHPMicroTemplate\Exception\SyntaxErrorException;
9+
use PHPMicroTemplate\Exception\UndefinedSymbolException;
710
use PHPModelGenerator\Exception\SchemaException;
811
use PHPModelGenerator\Model\Property\Property;
912
use PHPModelGenerator\Model\Property\PropertyInterface;
1013
use PHPModelGenerator\Model\Validator;
1114
use PHPModelGenerator\Model\Validator\AbstractComposedPropertyValidator;
15+
use PHPModelGenerator\Model\Validator\PropertyNamesValidator;
1216
use PHPModelGenerator\Model\Validator\PropertyTemplateValidator;
1317
use PHPModelGenerator\Model\Validator\PropertyValidator;
1418
use PHPModelGenerator\PropertyProcessor\ComposedValue\ComposedPropertiesInterface;
@@ -28,7 +32,15 @@ class BaseProcessor extends AbstractPropertyProcessor
2832
/**
2933
* @inheritdoc
3034
*
35+
* @param string $propertyName
36+
* @param array $propertyData
37+
*
38+
* @return PropertyInterface
39+
*
40+
* @throws FileSystemException
3141
* @throws SchemaException
42+
* @throws SyntaxErrorException
43+
* @throws UndefinedSymbolException
3244
*/
3345
public function process(string $propertyName, array $propertyData): PropertyInterface
3446
{
@@ -40,6 +52,7 @@ public function process(string $propertyName, array $propertyData): PropertyInte
4052
$property = new Property($propertyName, static::TYPE);
4153
$this->generateValidators($property, $propertyData);
4254

55+
$this->addPropertyNamesValidator($propertyData);
4356
$this->addAdditionalPropertiesValidator($propertyData);
4457
$this->addMinPropertiesValidator($propertyData);
4558
$this->addMaxPropertiesValidator($propertyData);
@@ -50,6 +63,32 @@ public function process(string $propertyName, array $propertyData): PropertyInte
5063
return $property;
5164
}
5265

66+
/**
67+
* Add a validator to check all provided property names
68+
*
69+
* @param array $propertyData
70+
*
71+
* @throws SchemaException
72+
* @throws FileSystemException
73+
* @throws SyntaxErrorException
74+
* @throws UndefinedSymbolException
75+
*/
76+
protected function addPropertyNamesValidator(array $propertyData): void
77+
{
78+
if (!isset($propertyData['propertyNames'])) {
79+
return;
80+
}
81+
82+
$this->schema->addBaseValidator(
83+
new PropertyNamesValidator(
84+
$this->propertyCollectionProcessor,
85+
$this->schemaProcessor,
86+
$this->schema,
87+
$propertyData['propertyNames']
88+
)
89+
);
90+
}
91+
5392
/**
5493
* Add an object validator to disallow properties which are not defined in the schema
5594
*

src/Templates/Exception/ComposedValueException.phptpl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
Invalid value for {{ propertyName }} declined by composition constraint.\n"
2-
. sprintf('{{ composedErrorMessage }}', $succeededCompositionElements)
2+
. sprintf(' {{ composedErrorMessage }}', $succeededCompositionElements)
33
. array_reduce(
44
$compositionErrorCollection,
55
function (string $carry, \PHPModelGeneratorException\ErrorRegistryExceptionInterface $exception) use (&$i) {
6-
return "$carry\n- Composition element #" . ++$i . (
6+
return "$carry\n - Composition element #" . ++$i . (
77
$exception->getErrors()
8-
? ": Failed\n * " . implode("\n * ", $exception->getErrors())
8+
? ": Failed\n * " . implode("\n * ", $exception->getErrors())
99
: ': Valid'
1010
);
1111
},
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Provided JSON contains properties with invalid names."
2+
. (function (array $invalidProperties) {
3+
$output = '';
4+
foreach ($invalidProperties as $propertyName => $errors) {
5+
$output .= "\n - invalid property '$propertyName'\n * " . implode("\n * ", $errors);
6+
}
7+
return $output;
8+
})($invalidProperties) . "
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
(function ($propertyNames) use (&$invalidProperties) {
2+
{% if generatorConfiguration.collectErrors() %}
3+
$originalErrorRegistry = $this->errorRegistry;
4+
{% endif %}
5+
6+
foreach ($propertyNames as $value) {
7+
// numerical array indices are auto casted to int
8+
$value = (string) $value;
9+
10+
try {
11+
{% if generatorConfiguration.collectErrors() %}
12+
$this->errorRegistry = new {{ viewHelper.getSimpleClassName(generatorConfiguration.getErrorRegistryClass()) }}();
13+
{% endif %}
14+
15+
{% foreach nameValidationProperty.getOrderedValidators() as validator %}
16+
{{ validator.getValidatorSetUp() }}
17+
if ({{ validator.getCheck() }}) {
18+
{{ viewHelper.validationError(validator.getExceptionMessage()) }}
19+
}
20+
{% endforeach %}
21+
22+
{% if generatorConfiguration.collectErrors() %}
23+
if ($this->errorRegistry->getErrors()) {
24+
$invalidProperties[$value] = $this->errorRegistry->getErrors();
25+
}
26+
{% endif %}
27+
} catch (\Exception $e) {
28+
// collect all errors concerning invalid property names
29+
isset($invalidProperties[$value])
30+
? $invalidProperties[$value][] = $e->getMessage()
31+
: $invalidProperties[$value] = [$e->getMessage()];
32+
}
33+
}
34+
35+
{% if generatorConfiguration.collectErrors() %}
36+
$this->errorRegistry = $originalErrorRegistry;
37+
{% endif %}
38+
39+
return !empty($invalidProperties);
40+
})(array_keys($modelData))

tests/Basic/ErrorCollectionTest.php

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -100,44 +100,44 @@ public function invalidValuesForCompositionDataProvider(): array
100100
6,
101101
<<<ERROR
102102
Invalid value for (.*?) declined by composition constraint\.
103-
Requires to match one composition element but matched 2 elements\.
104-
- Composition element #1: Valid
105-
- Composition element #2: Valid
103+
Requires to match one composition element but matched 2 elements\.
104+
- Composition element #1: Valid
105+
- Composition element #2: Valid
106106
ERROR
107107
],
108108
'too low number both' => [
109109
0,
110110
<<<ERROR
111111
Invalid value for (.*?) declined by composition constraint\.
112-
Requires to match one composition element but matched 0 elements\.
113-
- Composition element #1: Failed
114-
\* Value for integerProperty must not be smaller than 2
115-
- Composition element #2: Failed
116-
\* Value for integerProperty must not be smaller than 3
112+
Requires to match one composition element but matched 0 elements\.
113+
- Composition element #1: Failed
114+
\* Value for integerProperty must not be smaller than 2
115+
- Composition element #2: Failed
116+
\* Value for integerProperty must not be smaller than 3
117117
ERROR
118118
],
119119
'nothing matches' => [
120120
1,
121121
<<<ERROR
122122
Invalid value for (.*?) declined by composition constraint\.
123-
Requires to match one composition element but matched 0 elements\.
124-
- Composition element #1: Failed
125-
\* Value for integerProperty must not be smaller than 2
126-
\* Value for integerProperty must be a multiple of 2
127-
- Composition element #2: Failed
128-
\* Value for integerProperty must not be smaller than 3
129-
\* Value for integerProperty must be a multiple of 3
123+
Requires to match one composition element but matched 0 elements\.
124+
- Composition element #1: Failed
125+
\* Value for integerProperty must not be smaller than 2
126+
\* Value for integerProperty must be a multiple of 2
127+
- Composition element #2: Failed
128+
\* Value for integerProperty must not be smaller than 3
129+
\* Value for integerProperty must be a multiple of 3
130130
ERROR
131131
],
132132
'invalid type' => [
133133
"4",
134134
<<<ERROR
135135
Invalid value for (.*?) declined by composition constraint\.
136-
Requires to match one composition element but matched 0 elements\.
137-
- Composition element #1: Failed
138-
\* Invalid type for integerProperty. Requires int, got string
139-
- Composition element #2: Failed
140-
\* Invalid type for integerProperty. Requires int, got string
136+
Requires to match one composition element but matched 0 elements\.
137+
- Composition element #1: Failed
138+
\* Invalid type for integerProperty. Requires int, got string
139+
- Composition element #2: Failed
140+
\* Invalid type for integerProperty. Requires int, got string
141141
ERROR
142142
],
143143
];

0 commit comments

Comments
 (0)