Skip to content

Commit 178850c

Browse files
committed
Add additionalItems validation for tuple arrays
Move tuple tests into a separate test class
1 parent fe3167e commit 178850c

16 files changed

+655
-174
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/docs/build/
12
/tests/manual/result/
23
/vendor/
34
.idea
5+
composer.lock

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[![Latest Version](https://img.shields.io/packagist/v/wol-soft/php-json-schema-model-generator.svg)](https://packagist.org/packages/wol-soft/php-json-schema-model-generator)
2+
[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg)](https://php.net/)
23
[![Maintainability](https://api.codeclimate.com/v1/badges/9e3c565c528edb3d58d5/maintainability)](https://codeclimate.com/github/wol-soft/php-json-schema-model-generator/maintainability)
34
[![Test Coverage](https://api.codeclimate.com/v1/badges/7eb29e7366dc3d6a5f44/test_coverage)](https://codeclimate.com/github/wol-soft/php-json-schema-model-generator/test_coverage)
45
[![Build Status](https://travis-ci.org/wol-soft/php-micro-template.svg?branch=master)](https://travis-ci.org/wol-soft/php-json-schema-model-generator)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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\SchemaProcessor\SchemaProcessor;
13+
14+
/**
15+
* Class AdditionalItemsValidator
16+
*
17+
* @package PHPModelGenerator\Model\Validator
18+
*/
19+
class AdditionalItemsValidator extends AdditionalPropertiesValidator
20+
{
21+
protected const PROPERTY_NAME = 'additional item';
22+
23+
protected const PROPERTIES_KEY = 'items';
24+
protected const ADDITIONAL_PROPERTIES_KEY = 'additionalItems';
25+
26+
/** @var string */
27+
private $propertyName;
28+
29+
/**
30+
* AdditionalItemsValidator constructor.
31+
*
32+
* @param SchemaProcessor $schemaProcessor
33+
* @param Schema $schema
34+
* @param array $propertiesStructure
35+
* @param string $propertyName
36+
*
37+
* @throws FileSystemException
38+
* @throws SchemaException
39+
* @throws SyntaxErrorException
40+
* @throws UndefinedSymbolException
41+
*/
42+
public function __construct(
43+
SchemaProcessor $schemaProcessor,
44+
Schema $schema,
45+
array $propertiesStructure,
46+
string $propertyName
47+
) {
48+
$this->propertyName = $propertyName;
49+
50+
parent::__construct($schemaProcessor, $schema, $propertiesStructure);
51+
}
52+
53+
/**
54+
* Initialize all variables which are required to execute a property names validator
55+
*
56+
* @return string
57+
*/
58+
public function getValidatorSetUp(): string
59+
{
60+
return '
61+
$properties = $value;
62+
$invalidProperties = [];
63+
';
64+
}
65+
66+
protected function getErrorMessage(): string
67+
{
68+
return "Tuple array {$this->propertyName} contains invalid additional items.";
69+
}
70+
}

src/Model/Validator/AdditionalPropertiesValidator.php

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPMicroTemplate\Exception\UndefinedSymbolException;
1010
use PHPModelGenerator\Exception\SchemaException;
1111
use PHPModelGenerator\Model\Schema;
12+
use PHPModelGenerator\Model\Validator;
1213
use PHPModelGenerator\PropertyProcessor\PropertyCollectionProcessor;
1314
use PHPModelGenerator\PropertyProcessor\PropertyFactory;
1415
use PHPModelGenerator\PropertyProcessor\PropertyProcessorFactory;
@@ -22,45 +23,48 @@
2223
*/
2324
class AdditionalPropertiesValidator extends PropertyTemplateValidator
2425
{
26+
protected const PROPERTY_NAME = 'additional property';
27+
28+
protected const PROPERTIES_KEY = 'properties';
29+
protected const ADDITIONAL_PROPERTIES_KEY = 'additionalProperties';
30+
2531
/**
2632
* AdditionalPropertiesValidator constructor.
2733
*
2834
* @param SchemaProcessor $schemaProcessor
2935
* @param Schema $schema
30-
* @param array $tuplePropertiesStructure
36+
* @param array $propertiesStructure
3137
*
3238
* @throws FileSystemException
3339
* @throws SchemaException
3440
* @throws SyntaxErrorException
3541
* @throws UndefinedSymbolException
3642
*/
37-
public function __construct(
38-
SchemaProcessor $schemaProcessor,
39-
Schema $schema,
40-
array $tuplePropertiesStructure
41-
) {
43+
public function __construct(SchemaProcessor $schemaProcessor, Schema $schema, array $propertiesStructure) {
4244
$propertyFactory = new PropertyFactory(new PropertyProcessorFactory());
4345

4446
$validationProperty = $propertyFactory->create(
45-
new PropertyCollectionProcessor(),
47+
new PropertyCollectionProcessor([static::PROPERTY_NAME]),
4648
$schemaProcessor,
4749
$schema,
48-
'additional property',
49-
$tuplePropertiesStructure['additionalProperties']
50-
);
50+
static::PROPERTY_NAME,
51+
$propertiesStructure[static::ADDITIONAL_PROPERTIES_KEY]
52+
)->filterValidators(function (Validator $validator) {
53+
return !is_a($validator->getValidator(), RequiredPropertyValidator::class);
54+
});
5155

5256
parent::__construct(
5357
$this->getRenderer()->renderTemplate(
5458
DIRECTORY_SEPARATOR . 'Exception' . DIRECTORY_SEPARATOR . 'InvalidPropertiesException.phptpl',
55-
['error' => 'Provided JSON contains invalid additional properties.']
59+
['error' => $this->getErrorMessage(), 'property' => static::PROPERTY_NAME]
5660
),
5761
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'AdditionalProperties.phptpl',
5862
[
5963
'validationProperty' => $validationProperty,
6064
'additionalProperties' => preg_replace(
6165
'(\d+\s=>)',
6266
'',
63-
var_export(array_keys($tuplePropertiesStructure['properties'] ?? []), true)
67+
var_export(array_keys($propertiesStructure[static::PROPERTIES_KEY] ?? []), true)
6468
),
6569
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),
6670
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
@@ -75,6 +79,14 @@ public function __construct(
7579
*/
7680
public function getValidatorSetUp(): string
7781
{
78-
return '$invalidProperties = [];';
82+
return '
83+
$properties = $modelData;
84+
$invalidProperties = [];
85+
';
86+
}
87+
88+
protected function getErrorMessage(): string
89+
{
90+
return 'Provided JSON contains invalid additional properties.';
7991
}
8092
}

src/Model/Validator/ArrayTupleValidator.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class ArrayTupleValidator extends PropertyTemplateValidator
2828
*
2929
* @param SchemaProcessor $schemaProcessor
3030
* @param Schema $schema
31-
* @param array $tuplePropertiesStructure
31+
* @param array $propertiesStructure
3232
* @param string $propertyName
3333
*
3434
* @throws SchemaException
@@ -39,13 +39,13 @@ class ArrayTupleValidator extends PropertyTemplateValidator
3939
public function __construct(
4040
SchemaProcessor $schemaProcessor,
4141
Schema $schema,
42-
array $tuplePropertiesStructure,
42+
array $propertiesStructure,
4343
string $propertyName
4444
) {
4545
$propertyFactory = new PropertyFactory(new PropertyProcessorFactory());
4646

4747
$tupleProperties = [];
48-
foreach ($tuplePropertiesStructure as $tupleIndex => $tupleItem) {
48+
foreach ($propertiesStructure as $tupleIndex => $tupleItem) {
4949
$tupleItemName = "tuple item #$tupleIndex of array $propertyName";
5050

5151
// an item of the array behaves like a nested property to add item-level validation

src/Model/Validator/PropertyNamesValidator.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class PropertyNamesValidator extends PropertyTemplateValidator
2727
*
2828
* @param SchemaProcessor $schemaProcessor
2929
* @param Schema $schema
30-
* @param array $tuplePropertiesNames
30+
* @param array $propertiesNames
3131
*
3232
* @throws FileSystemException
3333
* @throws SyntaxErrorException
@@ -37,10 +37,10 @@ class PropertyNamesValidator extends PropertyTemplateValidator
3737
public function __construct(
3838
SchemaProcessor $schemaProcessor,
3939
Schema $schema,
40-
array $tuplePropertiesNames
40+
array $propertiesNames
4141
) {
4242
$nameValidationProperty = (new StringProcessor(new PropertyCollectionProcessor(), $schemaProcessor, $schema))
43-
->process('property name', $tuplePropertiesNames)
43+
->process('property name', $propertiesNames)
4444
// the property name validator doesn't need type checks or required checks so simply filter them out
4545
->filterValidators(function (Validator $validator) {
4646
return !is_a($validator->getValidator(), RequiredPropertyValidator::class) &&
@@ -50,7 +50,7 @@ public function __construct(
5050
parent::__construct(
5151
$this->getRenderer()->renderTemplate(
5252
DIRECTORY_SEPARATOR . 'Exception' . DIRECTORY_SEPARATOR . 'InvalidPropertiesException.phptpl',
53-
['error' => 'Provided JSON contains properties with invalid names.']
53+
['error' => 'Provided JSON contains properties with invalid names.', 'property' => 'property']
5454
),
5555
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'PropertyNames.phptpl',
5656
[

src/PropertyProcessor/Property/ArrayProcessor.php

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPMicroTemplate\Exception\UndefinedSymbolException;
1010
use PHPModelGenerator\Exception\SchemaException;
1111
use PHPModelGenerator\Model\Property\PropertyInterface;
12+
use PHPModelGenerator\Model\Validator\AdditionalItemsValidator;
1213
use PHPModelGenerator\Model\Validator\ArrayTupleValidator;
1314
use PHPModelGenerator\Model\Validator\PropertyTemplateValidator;
1415
use PHPModelGenerator\Model\Validator\PropertyValidator;
@@ -34,9 +35,12 @@ class ArrayProcessor extends AbstractTypedValueProcessor
3435

3536
/**
3637
* @param PropertyInterface $property
37-
* @param array $propertyData
38+
* @param array $propertyData
3839
*
40+
* @throws FileSystemException
3941
* @throws SchemaException
42+
* @throws SyntaxErrorException
43+
* @throws UndefinedSymbolException
4044
*/
4145
protected function generateValidators(PropertyInterface $property, array $propertyData): void
4246
{
@@ -103,7 +107,10 @@ private function addUniqueItemsValidation(PropertyInterface $property, array $pr
103107
* @param PropertyInterface $property
104108
* @param array $propertyData
105109
*
110+
* @throws FileSystemException
106111
* @throws SchemaException
112+
* @throws SyntaxErrorException
113+
* @throws UndefinedSymbolException
107114
*/
108115
private function addItemsValidation(PropertyInterface $property, array $propertyData): void
109116
{
@@ -142,18 +149,18 @@ private function addItemsValidation(PropertyInterface $property, array $property
142149
*/
143150
private function addTupleValidator(PropertyInterface $property, array $propertyData): void
144151
{
145-
if (isset($propertyData['additionalItems']) && $propertyData['additionalItems'] !== false) {
146-
// TODO: add validator by reusing the AdditionalPropertiesValidator
152+
if (isset($propertyData['additionalItems']) && $propertyData['additionalItems'] !== true) {
153+
$this->addAdditionalItemsValidator($property, $propertyData);
147154
}
148155

149156
$tupleCount = count($propertyData[self::JSON_FIELD_ITEMS]);
150157

151158
$property
152159
->addValidator(
153160
new PropertyValidator(
154-
'is_array($value) && count($value) < ' . $tupleCount,
161+
'is_array($value) && ($amount = count($value)) < ' . $tupleCount,
155162
sprintf(
156-
'Missing tuple item in array %s. Requires %s items, got " . count($value) . "',
163+
'Missing tuple item in array %s. Requires %s items, got $amount',
157164
$property->getName(),
158165
$tupleCount
159166
)
@@ -169,6 +176,44 @@ private function addTupleValidator(PropertyInterface $property, array $propertyD
169176
);
170177
}
171178

179+
/**
180+
* @param PropertyInterface $property
181+
* @param array $propertyData
182+
*
183+
* @throws FileSystemException
184+
* @throws SchemaException
185+
* @throws SyntaxErrorException
186+
* @throws UndefinedSymbolException
187+
*/
188+
private function addAdditionalItemsValidator(PropertyInterface $property, array $propertyData): void
189+
{
190+
if (!is_bool($propertyData['additionalItems'])) {
191+
$property->addValidator(
192+
new AdditionalItemsValidator(
193+
$this->schemaProcessor,
194+
$this->schema,
195+
$propertyData,
196+
$property->getName()
197+
)
198+
);
199+
200+
return;
201+
}
202+
203+
$expectedAmount = count($propertyData[self::JSON_FIELD_ITEMS]);
204+
205+
$property->addValidator(
206+
new PropertyValidator(
207+
'($amount = count($value)) > ' . $expectedAmount,
208+
sprintf(
209+
'Tuple array %s contains not allowed additional items. Expected %s items, got $amount',
210+
$property->getName(),
211+
$expectedAmount
212+
)
213+
)
214+
);
215+
}
216+
172217
/**
173218
* Add the validator to check for constraints required for at least one item
174219
*

src/Templates/Exception/InvalidPropertiesException.phptpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
. (function (array $invalidProperties) {
33
$output = '';
44
foreach ($invalidProperties as $propertyName => $errors) {
5-
$output .= "\n - invalid property '$propertyName'\n * " . implode("\n * ", $errors);
5+
$output .= "\n - invalid {{ property }} '$propertyName'\n * " . implode("\n * ", $errors);
66
}
77
return $output;
88
})($invalidProperties) . "

src/Templates/Validator/AdditionalProperties.phptpl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
(function () use ($modelData, &$invalidProperties) {
1+
(function () use ($properties, &$invalidProperties) {
22
{% if generatorConfiguration.collectErrors() %}
33
$originalErrorRegistry = $this->errorRegistry;
44
{% endif %}
55

6-
foreach (array_diff(array_keys($modelData), {{ additionalProperties }}) as $propertyKey) {
6+
foreach (array_diff(array_keys($properties), {{ additionalProperties }}) as $propertyKey) {
77
try {
8-
$value = $modelData[$propertyKey];
8+
$value = $properties[$propertyKey];
99

1010
{% if generatorConfiguration.collectErrors() %}
1111
$this->errorRegistry = new {{ viewHelper.getSimpleClassName(generatorConfiguration.getErrorRegistryClass()) }}();

0 commit comments

Comments
 (0)