Skip to content

Commit 06d3c69

Browse files
committed
Implement an option to deny additional properties by default (#31)
1 parent 5325e8d commit 06d3c69

File tree

7 files changed

+104
-2
lines changed

7 files changed

+104
-2
lines changed

docs/source/generator/postProcessor.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ AdditionalPropertiesAccessorPostProcessor
9292
9393
The **AdditionalPropertiesAccessorPostProcessor** adds methods to your model to work with `additional properties <../complexTypes/object.html#additional-properties>`__ on your objects. By default the post processor only adds methods to objects from a schema which defines constraints for additional properties. If the first constructor parameter *$addForModelsWithoutAdditionalPropertiesDefinition* is set to true the methods will also be added to objects generated from a schema which doesn't define additional properties constraints. If the *additionalProperties* keyword in a schema is set to false the methods will never be added.
9494

95+
.. note::
96+
97+
If the `deny additional properties setting <../gettingStarted.html#deny-additional-properties>`__ is set to true the setting *$addForModelsWithoutAdditionalPropertiesDefinition* is ignored as all objects which don't define additional properties are restricted to the defined properties
98+
9599
Added methods
96100
~~~~~~~~~~~~~
97101

docs/source/gettingStarted.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,22 @@ If the implicit null option is enabled the interface of your classes may change.
172172
(new GeneratorConfiguration())
173173
->setImplicitNull(true);
174174
175+
Deny additional properties
176+
^^^^^^^^^^^^^^^^^^^^^^^^^^
177+
178+
By default each generated object accepts additional properties. For strict property checks which error if undefined properties are provided each object must contain the *additionalProperties* kes set to *false*.
179+
180+
By setting the **denyAdditionalProperties** option each object which doesn't specify a value for *additionalProperties* is restricted to the defined properties.
181+
182+
.. code-block:: php
183+
184+
setDenyAdditionalProperties(bool $denyAdditionalProperties);
185+
186+
.. code-block:: php
187+
188+
(new GeneratorConfiguration())
189+
->setDenyAdditionalProperties(true);
190+
175191
Collect errors vs. early return
176192
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
177193

src/Model/GeneratorConfiguration.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class GeneratorConfiguration
2828
/** @var bool */
2929
protected $allowImplicitNull = false;
3030
/** @var bool */
31+
protected $denyAdditionalProperties = false;
32+
/** @var bool */
3133
protected $prettyPrint = false;
3234
/** @var bool */
3335
protected $outputEnabled = true;
@@ -177,6 +179,26 @@ public function setImmutable(bool $immutable): self
177179
return $this;
178180
}
179181

182+
/**
183+
* @return bool
184+
*/
185+
public function denyAdditionalProperties(): bool
186+
{
187+
return $this->denyAdditionalProperties;
188+
}
189+
190+
/**
191+
* @param bool $denyAdditionalProperties
192+
*
193+
* @return $this
194+
*/
195+
public function setDenyAdditionalProperties(bool $denyAdditionalProperties): self
196+
{
197+
$this->denyAdditionalProperties = $denyAdditionalProperties;
198+
199+
return $this;
200+
}
201+
180202
/**
181203
* @return bool
182204
*/

src/PropertyProcessor/Property/BaseProcessor.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ protected function addAdditionalPropertiesValidator(JsonSchema $propertySchema):
119119
{
120120
$json = $propertySchema->getJson();
121121

122+
if (!isset($json['additionalProperties']) &&
123+
$this->schemaProcessor->getGeneratorConfiguration()->denyAdditionalProperties()
124+
) {
125+
$json['additionalProperties'] = false;
126+
}
127+
122128
if (!isset($json['additionalProperties']) || $json['additionalProperties'] === true) {
123129
return;
124130
}

src/SchemaProcessor/PostProcessor/AdditionalPropertiesAccessorPostProcessor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public function process(Schema $schema, GeneratorConfiguration $generatorConfigu
5555

5656
if ((!$this->addForModelsWithoutAdditionalPropertiesDefinition && !isset($json['additionalProperties']))
5757
|| (isset($json['additionalProperties']) && $json['additionalProperties'] === false)
58+
|| (!isset($json['additionalProperties']) && $generatorConfiguration->denyAdditionalProperties())
5859
) {
5960
return;
6061
}

tests/Basic/AdditionalPropertiesTest.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ public function testAdditionalPropertiesAreIgnoredByDefault(array $propertyValue
3535
*/
3636
public function testAdditionalPropertiesAreIgnoredWhenSetToTrue(array $propertyValue): void
3737
{
38-
$className = $this->generateClassFromFileTemplate('AdditionalProperties.json', ['true']);
38+
$className = $this->generateClassFromFileTemplate(
39+
'AdditionalProperties.json',
40+
['true'],
41+
// make sure the deny additional properties setting doesn't affect specified additional properties
42+
(new GeneratorConfiguration())->setDenyAdditionalProperties(true)
43+
);
3944

4045
$object = new $className($propertyValue);
4146
$this->assertSame($propertyValue['name'] ?? null, $object->getName());
@@ -91,6 +96,27 @@ public function testAdditionalPropertiesThrowAnExceptionWhenSetToFalse(array $pr
9196
new $className($propertyValue);
9297
}
9398

99+
/**
100+
* @dataProvider additionalPropertiesDataProvider
101+
*
102+
* @param array $propertyValue
103+
*/
104+
public function testAdditionalPropertiesThrowAnExceptionWhenNotDefinedAndDeniedByGeneratorConfiguration(
105+
array $propertyValue
106+
): void {
107+
$this->expectException(AdditionalPropertiesException::class);
108+
$this->expectExceptionMessageMatches(
109+
'/Provided JSON for .* contains not allowed additional properties \[additional\]/'
110+
);
111+
112+
$className = $this->generateClassFromFile(
113+
'AdditionalPropertiesNotDefined.json',
114+
(new GeneratorConfiguration())->setDenyAdditionalProperties(true)->setCollectErrors(false)
115+
);
116+
117+
new $className($propertyValue);
118+
}
119+
94120
/**
95121
* @dataProvider validTypedAdditionalPropertiesDataProvider
96122
*

tests/PostProcessor/AdditionalPropertiesAccessorPostProcessorTest.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,29 @@ public function testAdditionalPropertiesAccessorsAreNotGeneratedForAdditionalPro
5757
$this->assertFalse(is_callable([$object, 'removeAdditionalProperty']));
5858
}
5959

60+
/**
61+
* @dataProvider additionalPropertiesAccessorPostProcessorConfigurationDataProvider
62+
*
63+
* @param bool $addForModelsWithoutAdditionalPropertiesDefinition
64+
*/
65+
public function testAdditionalPropertiesAccessorsAreNotGeneratedWhenAdditionalPropertiesAreDenied(
66+
bool $addForModelsWithoutAdditionalPropertiesDefinition
67+
): void {
68+
$this->addPostProcessor($addForModelsWithoutAdditionalPropertiesDefinition);
69+
70+
$className = $this->generateClassFromFile(
71+
'AdditionalPropertiesNotDefined.json',
72+
(new GeneratorConfiguration())->setDenyAdditionalProperties(true)
73+
);
74+
75+
$object = new $className();
76+
77+
$this->assertFalse(is_callable([$object, 'getAdditionalProperties']));
78+
$this->assertFalse(is_callable([$object, 'getAdditionalProperty']));
79+
$this->assertFalse(is_callable([$object, 'setAdditionalProperty']));
80+
$this->assertFalse(is_callable([$object, 'removeAdditionalProperty']));
81+
}
82+
6083
/**
6184
* @dataProvider additionalPropertiesAccessorPostProcessorConfigurationDataProvider
6285
*
@@ -103,7 +126,11 @@ public function testAdditionalPropertiesAccessorsAreGeneratedForAdditionalProper
103126
): void {
104127
$this->addPostProcessor($addForModelsWithoutAdditionalPropertiesDefinition);
105128

106-
$className = $this->generateClassFromFile('AdditionalProperties.json');
129+
$className = $this->generateClassFromFile(
130+
'AdditionalProperties.json',
131+
// make sure the deny additional properties setting doesn't affect specified additional properties
132+
(new GeneratorConfiguration())->setDenyAdditionalProperties(true)
133+
);
107134

108135
$object = new $className(['property1' => 'Hello', 'property2' => 'World']);
109136

0 commit comments

Comments
 (0)