diff --git a/src/Model/Validator/ExtractedMethodValidator.php b/src/Model/Validator/ExtractedMethodValidator.php index c1b8cc56..6a63b616 100644 --- a/src/Model/Validator/ExtractedMethodValidator.php +++ b/src/Model/Validator/ExtractedMethodValidator.php @@ -59,7 +59,7 @@ public function __construct( public function getCode(): string { $renderHelper = new RenderHelper($this->generatorConfiguration); - return "private function {$this->validator->getExtractedMethodName()}(&\$value): void { + return "private function {$this->validator->getExtractedMethodName()}(&\$value, \$modelData): void { {$this->validator->getValidatorSetUp()} if ({$this->validator->getCheck()}) { diff --git a/src/Model/Validator/PropertyNamesValidator.php b/src/Model/Validator/PropertyNamesValidator.php index 019655ff..e738e553 100644 --- a/src/Model/Validator/PropertyNamesValidator.php +++ b/src/Model/Validator/PropertyNamesValidator.php @@ -10,6 +10,7 @@ use PHPModelGenerator\Model\Schema; use PHPModelGenerator\Model\SchemaDefinition\JsonSchema; use PHPModelGenerator\Model\Validator; +use PHPModelGenerator\PropertyProcessor\Property\ConstProcessor; use PHPModelGenerator\PropertyProcessor\Property\StringProcessor; use PHPModelGenerator\PropertyProcessor\PropertyMetaDataCollection; use PHPModelGenerator\SchemaProcessor\SchemaProcessor; @@ -38,7 +39,15 @@ public function __construct( ) { $this->isResolved = true; - $nameValidationProperty = (new StringProcessor(new PropertyMetaDataCollection(), $schemaProcessor, $schema)) + $processor = array_key_exists('const', $propertiesNames->getJson()) + ? ConstProcessor::class + : StringProcessor::class; + + if ($processor === ConstProcessor::class && gettype($propertiesNames->getJson()['const']) !== 'string') { + throw new SchemaException("Invalid const property name in file {$propertiesNames->getFile()}"); + } + + $nameValidationProperty = (new $processor(new PropertyMetaDataCollection(), $schemaProcessor, $schema)) ->process('property name', $propertiesNames) // the property name validator doesn't need type checks or required checks so simply filter them out ->filterValidators(static function (Validator $validator): bool { diff --git a/src/PropertyProcessor/Property/AbstractPropertyProcessor.php b/src/PropertyProcessor/Property/AbstractPropertyProcessor.php index e041c810..31984003 100644 --- a/src/PropertyProcessor/Property/AbstractPropertyProcessor.php +++ b/src/PropertyProcessor/Property/AbstractPropertyProcessor.php @@ -213,7 +213,9 @@ protected function addComposedValueValidator(PropertyInterface $property, JsonSc $propertySchema->withJson([ 'type' => $composedValueKeyword, 'propertySchema' => $propertySchema, - 'onlyForDefinedValues' => !($this instanceof BaseProcessor) && !$property->isRequired(), + 'onlyForDefinedValues' => !($this instanceof BaseProcessor) && + (!$property->isRequired() + && $this->schemaProcessor->getGeneratorConfiguration()->isImplicitNullAllowed()), ]), ); diff --git a/src/PropertyProcessor/Property/ArrayProcessor.php b/src/PropertyProcessor/Property/ArrayProcessor.php index 8937e2a0..0057fcbe 100644 --- a/src/PropertyProcessor/Property/ArrayProcessor.php +++ b/src/PropertyProcessor/Property/ArrayProcessor.php @@ -16,11 +16,13 @@ use PHPModelGenerator\Model\Property\PropertyInterface; use PHPModelGenerator\Model\Property\PropertyType; use PHPModelGenerator\Model\SchemaDefinition\JsonSchema; +use PHPModelGenerator\Model\Validator; use PHPModelGenerator\Model\Validator\AdditionalItemsValidator; use PHPModelGenerator\Model\Validator\ArrayItemValidator; use PHPModelGenerator\Model\Validator\ArrayTupleValidator; use PHPModelGenerator\Model\Validator\PropertyTemplateValidator; use PHPModelGenerator\Model\Validator\PropertyValidator; +use PHPModelGenerator\Model\Validator\RequiredPropertyValidator; use PHPModelGenerator\PropertyProcessor\Decorator\Property\DefaultArrayToEmptyArrayDecorator; use PHPModelGenerator\PropertyProcessor\PropertyMetaDataCollection; use PHPModelGenerator\PropertyProcessor\PropertyFactory; @@ -253,16 +255,21 @@ private function addContainsValidation(PropertyInterface $property, JsonSchema $ return; } + $name = "item of array {$property->getName()}"; // an item of the array behaves like a nested property to add item-level validation $nestedProperty = (new PropertyFactory(new PropertyProcessorFactory())) ->create( - new PropertyMetaDataCollection(), + new PropertyMetaDataCollection([$name]), $this->schemaProcessor, $this->schema, - "item of array {$property->getName()}", + $name, $propertySchema->withJson($propertySchema->getJson()[self::JSON_FIELD_CONTAINS]), ); + $nestedProperty->filterValidators(static function (Validator $validator): bool { + return !is_a($validator->getValidator(), RequiredPropertyValidator::class); + }); + $property->addValidator( new PropertyTemplateValidator( $property, diff --git a/src/PropertyProcessor/Property/ConstProcessor.php b/src/PropertyProcessor/Property/ConstProcessor.php index 9ca86d8e..d5ad8097 100644 --- a/src/PropertyProcessor/Property/ConstProcessor.php +++ b/src/PropertyProcessor/Property/ConstProcessor.php @@ -10,7 +10,7 @@ use PHPModelGenerator\Model\Property\PropertyType; use PHPModelGenerator\Model\SchemaDefinition\JsonSchema; use PHPModelGenerator\Model\Validator\PropertyValidator; -use PHPModelGenerator\PropertyProcessor\PropertyProcessorInterface; +use PHPModelGenerator\Utils\RenderHelper; use PHPModelGenerator\Utils\TypeConverter; /** @@ -18,7 +18,7 @@ * * @package PHPModelGenerator\PropertyProcessor\Property */ -class ConstProcessor implements PropertyProcessorInterface +class ConstProcessor extends AbstractPropertyProcessor { /** * @inheritdoc @@ -34,13 +34,26 @@ public function process(string $propertyName, JsonSchema $propertySchema): Prope $json['description'] ?? '', ); - return $property - ->setRequired(true) - ->addValidator(new PropertyValidator( - $property, - '$value !== ' . var_export($json['const'], true), - InvalidConstException::class, - [$json['const']], - )); + $property->setRequired($this->propertyMetaDataCollection->isAttributeRequired($propertyName)); + + $check = match(true) { + $property->isRequired() + => '$value !== ' . var_export($json['const'], true), + $this->isImplicitNullAllowed($property) + => '!in_array($value, ' . RenderHelper::varExportArray([$json['const'], null]) . ', true)', + default + => "array_key_exists('{$property->getName()}', \$modelData) && \$value !== " . var_export($json['const'], true), + }; + + $property->addValidator(new PropertyValidator( + $property, + $check, + InvalidConstException::class, + [$json['const']], + )); + + $this->generateValidators($property, $propertySchema); + + return $property; } } diff --git a/src/Templates/Validator/AdditionalProperties.phptpl b/src/Templates/Validator/AdditionalProperties.phptpl index 87557485..96c93e64 100644 --- a/src/Templates/Validator/AdditionalProperties.phptpl +++ b/src/Templates/Validator/AdditionalProperties.phptpl @@ -1,4 +1,4 @@ -(function () use ($properties, &$invalidProperties) { +(function () use ($properties, &$invalidProperties, $modelData) { {% if generatorConfiguration.collectErrors() %} $originalErrorRegistry = $this->_errorRegistry; {% endif %} diff --git a/src/Templates/Validator/ArrayItem.phptpl b/src/Templates/Validator/ArrayItem.phptpl index 0c14f334..cdbea591 100644 --- a/src/Templates/Validator/ArrayItem.phptpl +++ b/src/Templates/Validator/ArrayItem.phptpl @@ -1,4 +1,4 @@ -is_array($value) && (function (&$items) use (&$invalidItems{{ suffix }}) { +is_array($value) && (function (&$items) use (&$invalidItems{{ suffix }}, $modelData) { {% if generatorConfiguration.collectErrors() %} $originalErrorRegistry = $this->_errorRegistry; {% endif %} diff --git a/src/Templates/Validator/ArrayTuple.phptpl b/src/Templates/Validator/ArrayTuple.phptpl index 425e9d11..d6ce49ea 100644 --- a/src/Templates/Validator/ArrayTuple.phptpl +++ b/src/Templates/Validator/ArrayTuple.phptpl @@ -1,4 +1,4 @@ -is_array($value) && (function (&$items) use (&$invalidTuples) { +is_array($value) && (function (&$items) use (&$invalidTuples, $modelData) { {% if generatorConfiguration.collectErrors() %} $originalErrorRegistry = $this->_errorRegistry; {% endif %} diff --git a/src/Templates/Validator/PatternProperties.phptpl b/src/Templates/Validator/PatternProperties.phptpl index b69d4c13..42bb0094 100644 --- a/src/Templates/Validator/PatternProperties.phptpl +++ b/src/Templates/Validator/PatternProperties.phptpl @@ -1,4 +1,4 @@ -(function () use ($properties, &$invalidProperties) { +(function () use ($properties, &$invalidProperties, $modelData) { {% if generatorConfiguration.collectErrors() %} $originalErrorRegistry = $this->_errorRegistry; {% endif %} diff --git a/src/Utils/RenderHelper.php b/src/Utils/RenderHelper.php index 4d249350..c69daaad 100644 --- a/src/Utils/RenderHelper.php +++ b/src/Utils/RenderHelper.php @@ -182,7 +182,7 @@ public function renderValidator(PropertyValidatorInterface $validator, Schema $s $schema->addMethod($validator->getExtractedMethodName(), $validator->getMethod()); } - return "\$this->{$validator->getExtractedMethodName()}(\$value);"; + return "\$this->{$validator->getExtractedMethodName()}(\$value, \$modelData);"; } public function renderMethods(Schema $schema): string diff --git a/tests/Basic/PropertyNamesTest.php b/tests/Basic/PropertyNamesTest.php index 692c641c..42dd90f3 100644 --- a/tests/Basic/PropertyNamesTest.php +++ b/tests/Basic/PropertyNamesTest.php @@ -4,6 +4,7 @@ namespace PHPModelGenerator\Tests\Basic; +use PHPModelGenerator\Exception\SchemaException; use PHPModelGenerator\Model\GeneratorConfiguration; use PHPModelGenerator\Tests\AbstractPHPModelGeneratorTestCase; @@ -84,6 +85,12 @@ public function validPropertyNamesDataProvider(): array 'test1298398717931793179317937197931' => 2, ], ], + 'const' => [ + '{"const": "test"}', + [ + 'test' => 1, + ], + ], ], ); } @@ -166,6 +173,21 @@ public function invalidPropertyNamesDataProvider(): array * Value for property name doesn't match pattern ^test[0-9]+$ - invalid property 'test' * Value for property name doesn't match pattern ^test[0-9]+$ +ERROR + ], + 'const violation' => [ + '{"const": "test"}', + [ + 'test1' => 1, + 'test' => 2, + 'bla' => 3, + ], + <<expectException(SchemaException::class); + $this->expectExceptionMessageMatches('/Invalid const property name in file/'); + + $this->generateClassFromFileTemplate('PropertyNames.json', ['{"const": false}'], escape: false); + } } diff --git a/tests/Objects/ArrayPropertyTest.php b/tests/Objects/ArrayPropertyTest.php index 4f855ccd..13c34c6e 100644 --- a/tests/Objects/ArrayPropertyTest.php +++ b/tests/Objects/ArrayPropertyTest.php @@ -683,7 +683,6 @@ public function validArrayContainsDataProvider(): array return $this->combineDataProvider( $this->validationMethodDataProvider(), [ - 'null' => [[3, null, true]], 'empty string' => [[3, '', true]], 'lowercase string' => [[3, 'abc', true]], 'uppercase string' => [[3, 'AB', true]], diff --git a/tests/Objects/ConstPropertyTest.php b/tests/Objects/ConstPropertyTest.php index 3f87ec06..4ed86647 100644 --- a/tests/Objects/ConstPropertyTest.php +++ b/tests/Objects/ConstPropertyTest.php @@ -4,10 +4,19 @@ namespace PHPModelGenerator\Tests\Objects; +use PHPModelGenerator\Exception\Arrays\ContainsException; +use PHPModelGenerator\Exception\Arrays\InvalidItemException; +use PHPModelGenerator\Exception\Arrays\InvalidTupleException; +use PHPModelGenerator\Exception\ComposedValue\OneOfException; +use PHPModelGenerator\Exception\ErrorRegistryException; use PHPModelGenerator\Exception\FileSystemException; +use PHPModelGenerator\Exception\Object\InvalidAdditionalPropertiesException; +use PHPModelGenerator\Exception\Object\InvalidPatternPropertiesException; +use PHPModelGenerator\Exception\Object\RequiredValueException; use PHPModelGenerator\Exception\ValidationException; use PHPModelGenerator\Exception\RenderException; use PHPModelGenerator\Exception\SchemaException; +use PHPModelGenerator\Model\GeneratorConfiguration; use PHPModelGenerator\Tests\AbstractPHPModelGeneratorTestCase; use stdClass; @@ -34,18 +43,124 @@ public function testProvidedConstPropertyIsValid(): void } /** - * @throws FileSystemException - * @throws RenderException - * @throws SchemaException + * @dataProvider nestedConstStructureDataProvider */ - public function testNotProvidedConstPropertyThrowsAnException(): void + public function testNotProvidedOptionalNestedConstPropertyIsValid(string $file): void + { + $className = $this->generateClassFromFile($file); + + $object = new $className([]); + + $this->assertNull($object->getProperty()); + } + + public function nestedConstStructureDataProvider(): array + { + return [ + 'array tuple' => ['ArrayTupleConstProperty.json'], + 'array item' => ['ArrayItemConstProperty.json'], + 'oneOf' => ['OneOfConstProperty.json'], + ]; + } + + /** + * @dataProvider validArrayConstValues + */ + public function testProvidedArrayConstPropertyIsValid(string $file, ?array $value): void + { + $className = $this->generateClassFromFile($file); + + $object = new $className(['property' => $value]); + + $this->assertSame($value, $object->getProperty()); + } + + public function validArrayConstValues(): array + { + return $this->combineDataProvider( + [ + 'array tuple' => ['ArrayTupleConstProperty.json'], + 'array item' => ['ArrayItemConstProperty.json'], + ], + [ + 'item provided' => [['red']], + 'multiple items provided' => [['red', 'red']], + 'null provided' => [null], + ], + ); + } + + /** + * @dataProvider invalidArrayConstDataProvider + */ + public function testNotMatchingArrayConstPropertyThrowsAnException( + string $file, + string $exception, + array $value, + ): void { + $this->expectException($exception); + + $className = $this->generateClassFromFile($file); + + new $className(['property' => $value]); + } + + public function invalidArrayConstDataProvider(): array + { + return $this->combineDataProvider( + [ + 'array tuple' => ['ArrayTupleConstProperty.json', InvalidTupleException::class], + 'array item' => ['ArrayItemConstProperty.json', InvalidItemException::class], + ], + [ + 'invalid item' => [['green']], + 'invalid item (multiple items)' => [['green', 'red']], + 'null' => [[null]], + ], + ); + } + + /** + * @dataProvider nestedConstStructureDataProvider + */ + public function testNullForNestedConstPropertyWithImplicitNullDisabledThrowsAnException(string $file): void { $this->expectException(ValidationException::class); - $this->expectExceptionMessage('Invalid value for stringProperty declined by const constraint'); - $className = $this->generateClassFromFile('ConstProperty.json'); + $className = $this->generateClassFromFile($file, implicitNull: false); - new $className([]); + new $className(['property' => null]); + } + + /** + * @dataProvider validOneOfDataProvider + */ + public function testProvidedOneOfConstPropertyIsValid(mixed $propertyValue): void + { + $className = $this->generateClassFromFile('OneOfConstProperty.json'); + + $object = new $className(['property' => $propertyValue]); + + $this->assertSame($propertyValue, $object->getProperty()); + } + + public function validOneOfDataProvider(): array + { + return [ + 'first branch' => ['red'], + 'second branch' => [1], + 'implicit null' => [null], + ]; + } + + public function testNotMatchingOneOfPropertyThrowsAnException(): void + { + $this->expectException(OneOfException::class); + $this->expectExceptionMessage('Invalid value for property declined by composition constraint'); + + $className = $this->generateClassFromFile('OneOfConstProperty.json'); + + new $className(['property' => 'green']); } /** @@ -62,7 +177,7 @@ public function testNotMatchingProvidedDataThrowsAnException($propertyValue): vo $this->expectException(ValidationException::class); $this->expectExceptionMessage('Invalid value for stringProperty declined by const constraint'); - $className = $this->generateClassFromFile('ConstProperty.json'); + $className = $this->generateClassFromFile('ConstProperty.json', null, false, false); new $className(['stringProperty' => $propertyValue]); } @@ -79,4 +194,289 @@ public function invalidPropertyDataProvider(): array 'null' => [null], ]; } + + /** + * @throws FileSystemException + * @throws RenderException + * @throws SchemaException + */ + public function testProvidedConstOnlyRequiredPropertyIsValid(): void + { + $className = $this->generateClassFromFile('RequiredAndOptionalConstProperties.json'); + + $object = new $className(['requiredProperty' => 'red']); + + $this->assertSame('red', $object->getRequiredProperty()); + $this->assertNull($object->getOptionalProperty()); + } + + /** + * @throws FileSystemException + * @throws RenderException + * @throws SchemaException + */ + public function testProvidedNullOptionalPropertyConstPropertyIsValid(): void + { + $className = $this->generateClassFromFile('RequiredAndOptionalConstProperties.json'); + + $object = new $className(['requiredProperty' => 'red', 'optionalProperty' => null]); + + $this->assertSame('red', $object->getRequiredProperty()); + $this->assertNull($object->getOptionalProperty()); + } + + /** + * @dataProvider requiredAndOptionalPropertiesDataProvider + * + * @throws FileSystemException + * @throws RenderException + * @throws SchemaException + */ + public function testProvidedConstPropertiesIsValidWithDifferentImplicitNull( + bool $implicitNull, + string $reqPropertyValue, + string $optPropertyValue + ): void { + $className = $this->generateClassFromFile( + 'RequiredAndOptionalConstProperties.json', + new GeneratorConfiguration(), + false, + $implicitNull, + ); + + $object = new $className(['requiredProperty' => $reqPropertyValue, 'optionalProperty' => $optPropertyValue]); + + $this->assertSame($reqPropertyValue, $object->getRequiredProperty()); + $this->assertSame($optPropertyValue, $object->getOptionalProperty()); + + // typing for required const + $this->assertSame('string', $this->getPropertyTypeAnnotation($object, 'requiredProperty')); + + $this->assertSame('string', $this->getReturnTypeAnnotation($object, 'getRequiredProperty')); + $returnType = $this->getReturnType($object, 'getRequiredProperty'); + $this->assertSame('string', $returnType->getName()); + $this->assertFalse($returnType->allowsNull()); + + $this->assertSame('string', $this->getParameterTypeAnnotation($className, 'setRequiredProperty'), + ); + $setAgeParamType = $this->getParameterType($className, 'setRequiredProperty'); + $this->assertSame('string', $setAgeParamType->getName()); + $this->assertFalse($returnType->allowsNull()); + + // typing for optional const + $this->assertSame('string|null', $this->getPropertyTypeAnnotation($object, 'optionalProperty')); + + $this->assertSame('string|null', $this->getReturnTypeAnnotation($object, 'getOptionalProperty')); + $returnType = $this->getReturnType($object, 'getOptionalProperty'); + $this->assertSame('string', $returnType->getName()); + $this->assertTrue($returnType->allowsNull()); + + $this->assertSame( + $implicitNull ? 'string|null' : 'string', + $this->getParameterTypeAnnotation($className, 'setOptionalProperty'), + ); + $setAgeParamType = $this->getParameterType($className, 'setOptionalProperty'); + $this->assertSame('string', $setAgeParamType->getName()); + $this->assertSame($implicitNull, $setAgeParamType->allowsNull()); + } + + public function requiredAndOptionalPropertiesDataProvider(): array + { + return $this->combineDataProvider( + $this->implicitNullDataProvider(), + [ + ['red', 'green'], + ], + ); + } + + /** + * @throws FileSystemException + * @throws RenderException + * @throws SchemaException + */ + public function testNotProvidedRequiredPropertyThrowsAnException(): void + { + $this->expectException(RequiredValueException::class); + $this->expectExceptionMessage('Missing required value for requiredProperty'); + + $className = $this->generateClassFromFile('RequiredAndOptionalConstProperties.json'); + + new $className([]); + } + + /** + * @dataProvider invalidRequiredAndOptionalConstPropertiesDataProvider + * + * @throws FileSystemException + * @throws RenderException + * @throws SchemaException + */ + public function testNotMatchingRequiredAndOptionalProvidedDataThrowsAnException( + bool $implicitNull, + string $reqPropertyValue, + ?string $optPropertyValue, + string $exceptionMessage + ): void + { + $className = $this->generateClassFromFile( + 'RequiredAndOptionalConstProperties.json', + new GeneratorConfiguration(), + false, + $implicitNull, + ); + + $this->expectException(ErrorRegistryException::class); + $this->expectExceptionMessage($exceptionMessage); + + new $className(['requiredProperty' => $reqPropertyValue, 'optionalProperty' => $optPropertyValue]); + } + + public function invalidRequiredAndOptionalConstPropertiesDataProvider(): array + { + return $this->combineDataProvider( + $this->implicitNullDataProvider(), + [ + ['blue', 'green', 'Invalid value for requiredProperty declined by const constraint'], + ['blue', null, 'Invalid value for requiredProperty declined by const constraint'], + ['red', 'blue', 'Invalid value for optionalProperty declined by const constraint'], + ['red', '0', 'Invalid value for optionalProperty declined by const constraint'], + ['red', '', 'Invalid value for optionalProperty declined by const constraint'], + ], + ); + } + + /** + * @dataProvider implicitNullDataProvider + */ + public function testProvidedNullValueConstPropertyIsValid(bool $implicitNull): void + { + $className = $this->generateClassFromFile('NullValueConstProperty.json', implicitNull: $implicitNull); + + $object = new $className(['nullProperty' => null]); + + $this->assertNull($object->getNullProperty()); + } + + /** + * @dataProvider validConstAdditionalPropertiesDataProvider + */ + public function testValidConstAdditionalProperties(array $value): void + { + $className = $this->generateClassFromFile('AdditionalPropertiesConst.json'); + + $object = new $className($value); + + $this->assertSame($value, $object->getRawModelDataInput()); + } + + public function validConstAdditionalPropertiesDataProvider(): array + { + return [ + 'no properties' => [[]], + 'one property' => [['property1' => 'red']], + 'multiple properties' => [['property1' => 'red', 'property2' => 'red']], + ]; + } + + /** + * @dataProvider invalidConstAdditionalPropertiesDataProvider + */ + public function testInvalidConstAdditionalPropertiesThrowsAnException(array $value): void + { + $this->expectException(InvalidAdditionalPropertiesException::class); + $this->expectExceptionMessageMatches('/Invalid value for additional property declined by const constraint/'); + + $className = $this->generateClassFromFile('AdditionalPropertiesConst.json'); + + new $className($value); + } + + public function invalidConstAdditionalPropertiesDataProvider(): array + { + return [ + 'null' => [['property1' => null]], + 'invalid value' => [['property1' => 'green']], + 'mixed valid and invalid values' => [['property1' => 'red', 'property2' => 'green']], + ]; + } + + /** + * @dataProvider validConstPatternPropertiesDataProvider + */ + public function testValidConstPatternProperties(array $value): void + { + $className = $this->generateClassFromFile('PatternPropertiesConst.json'); + + $object = new $className($value); + + $this->assertSame($value, $object->getRawModelDataInput()); + } + + public function validConstPatternPropertiesDataProvider(): array + { + return [ + 'no properties' => [[]], + 'one property' => [['property1' => 'red']], + 'multiple properties' => [['property1' => 'red', 'property2' => 'red']], + 'not matching property' => [['different' => 'green']], + ]; + } + + /** + * @dataProvider invalidConstAdditionalPropertiesDataProvider + */ + public function testInvalidConstPatternPropertiesThrowsAnException(array $value): void + { + $this->expectException(InvalidPatternPropertiesException::class); + $this->expectExceptionMessageMatches('/Invalid value for pattern property declined by const constraint/'); + + $className = $this->generateClassFromFile('PatternPropertiesConst.json'); + + new $className($value); + } + + + /** + * @dataProvider validConstArrayContainsDataProvider + */ + public function testValidConstArrayContains(array $value): void + { + $className = $this->generateClassFromFile('ArrayContainsConst.json'); + + $object = new $className(['property' => $value]); + + $this->assertSame($value, $object->getProperty()); + } + + public function validConstArrayContainsDataProvider(): array + { + return [ + 'one item' => [['red']], + 'multiple items all matching' => [['red', 'red']], + 'multiple items one matching' => [['green', 'red', 'yellow']], + ]; + } + + /** + * @dataProvider invalidConstArrayContainsDataProvider + */ + public function testInvalidConstArrayContainsThrowsAnException(array $value): void + { + $this->expectException(ContainsException::class); + $this->expectExceptionMessage('No item in array property matches contains constraint'); + + $className = $this->generateClassFromFile('ArrayContainsConst.json'); + + new $className(['property' => $value]); + } + + public function invalidConstArrayContainsDataProvider(): array + { + return [ + 'empty array' => [[]], + 'null' => [[null]], + 'value not in array' => [['green', 'yellow', 'blue']], + ]; + } } diff --git a/tests/Schema/ConstPropertyTest/AdditionalPropertiesConst.json b/tests/Schema/ConstPropertyTest/AdditionalPropertiesConst.json new file mode 100644 index 00000000..4513ce30 --- /dev/null +++ b/tests/Schema/ConstPropertyTest/AdditionalPropertiesConst.json @@ -0,0 +1,6 @@ +{ + "type": "object", + "additionalProperties": { + "const": "red" + } +} \ No newline at end of file diff --git a/tests/Schema/ConstPropertyTest/ArrayContainsConst.json b/tests/Schema/ConstPropertyTest/ArrayContainsConst.json new file mode 100644 index 00000000..fbca23b1 --- /dev/null +++ b/tests/Schema/ConstPropertyTest/ArrayContainsConst.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "properties": { + "property": { + "type": "array", + "contains": { + "const": "red" + } + } + } +} \ No newline at end of file diff --git a/tests/Schema/ConstPropertyTest/ArrayItemConstProperty.json b/tests/Schema/ConstPropertyTest/ArrayItemConstProperty.json new file mode 100644 index 00000000..7620d710 --- /dev/null +++ b/tests/Schema/ConstPropertyTest/ArrayItemConstProperty.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "properties": { + "property": { + "type": "array", + "items": { + "const": "red" + } + } + } +} \ No newline at end of file diff --git a/tests/Schema/ConstPropertyTest/ArrayTupleConstProperty.json b/tests/Schema/ConstPropertyTest/ArrayTupleConstProperty.json new file mode 100644 index 00000000..c111e74b --- /dev/null +++ b/tests/Schema/ConstPropertyTest/ArrayTupleConstProperty.json @@ -0,0 +1,13 @@ +{ + "type": "object", + "properties": { + "property": { + "type": "array", + "items": [ + { + "const": "red" + } + ] + } + } +} \ No newline at end of file diff --git a/tests/Schema/ConstPropertyTest/NullValueConstProperty.json b/tests/Schema/ConstPropertyTest/NullValueConstProperty.json new file mode 100644 index 00000000..8ee4dc45 --- /dev/null +++ b/tests/Schema/ConstPropertyTest/NullValueConstProperty.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "properties": { + "nullProperty": { + "const": null + } + }, + "required": [ + "nullProperty" + ] +} \ No newline at end of file diff --git a/tests/Schema/ConstPropertyTest/OneOfConstProperty.json b/tests/Schema/ConstPropertyTest/OneOfConstProperty.json new file mode 100644 index 00000000..94d9539f --- /dev/null +++ b/tests/Schema/ConstPropertyTest/OneOfConstProperty.json @@ -0,0 +1,15 @@ +{ + "type": "object", + "properties": { + "property": { + "oneOf": [ + { + "const": "red" + }, + { + "const": 1 + } + ] + } + } +} \ No newline at end of file diff --git a/tests/Schema/ConstPropertyTest/PatternPropertiesConst.json b/tests/Schema/ConstPropertyTest/PatternPropertiesConst.json new file mode 100644 index 00000000..885e885d --- /dev/null +++ b/tests/Schema/ConstPropertyTest/PatternPropertiesConst.json @@ -0,0 +1,8 @@ +{ + "type": "object", + "patternProperties": { + "property\\d+": { + "const": "red" + } + } +} \ No newline at end of file diff --git a/tests/Schema/ConstPropertyTest/RequiredAndOptionalConstProperties.json b/tests/Schema/ConstPropertyTest/RequiredAndOptionalConstProperties.json new file mode 100644 index 00000000..06987a0a --- /dev/null +++ b/tests/Schema/ConstPropertyTest/RequiredAndOptionalConstProperties.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "properties": { + "requiredProperty": { + "const": "red" + }, + "optionalProperty": { + "const": "green" + } + }, + "required": [ + "requiredProperty" + ] +} \ No newline at end of file diff --git a/tests/manual/schema/person.json b/tests/manual/schema/person.json index 0296a97a..1a946931 100644 --- a/tests/manual/schema/person.json +++ b/tests/manual/schema/person.json @@ -1,19 +1,15 @@ { - "$id": "Person", "type": "object", "properties": { - "name": { - "type": "string", - "description": "The name of the person", - "example": "Lawrence" - }, - "age": { - "type": "integer", - "description": "The age of the person", - "example": 42 + "property": { + "oneOf": [ + { + "const": "1" + }, + { + "const": "2" + } + ] } - }, - "required": [ - "name" - ] + } } \ No newline at end of file diff --git a/tests/manual/test.php b/tests/manual/test.php index 60456579..87d6c557 100644 --- a/tests/manual/test.php +++ b/tests/manual/test.php @@ -8,9 +8,15 @@ $generator = new ModelGenerator((new GeneratorConfiguration()) ->setNamespacePrefix('\\ManualSchema') - ->setImmutable(false), + ->setImmutable(false) + ->setCollectErrors(false) + ->setImplicitNull(false) ); $generator ->generateModelDirectory(__DIR__ . '/result') ->generateModels(new RecursiveDirectoryProvider(__DIR__ . '/schema'), __DIR__ . '/result'); + +$p = new \ManualSchema\Person(['property' => 1]); + +var_dump($p->getProperty());