Skip to content

Commit

Permalink
Merge pull request #1 from PackageFactory/feature/dataStructureValidator
Browse files Browse the repository at this point in the history
FEATURE: Add dataStructureValidator
  • Loading branch information
mficzel authored Nov 9, 2018
2 parents ba5ddd8 + 25890c9 commit f9bf281
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 0 deletions.
60 changes: 60 additions & 0 deletions Classes/Validators/DataStructureValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
namespace PackageFactory\AtomicFusion\PropTypes\Validators;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Validation\Validator\AbstractValidator;
use Neos\Flow\Validation\Validator\ValidatorInterface;
use Neos\Utility\ObjectAccess;

/**
* Validator for data-structures.
*/
class DataStructureValidator extends AbstractValidator
{
protected $acceptsEmptyValues = false;

/**
* @var array
*/
protected $supportedOptions = [
'dataStructure' => array([], 'The expected data-structure for this property', 'array')
];

/**
* Checks if the given value is accepted.
*
* @param mixed $value The value that should be validated
* @return void
*/
protected function isValid($value)
{
if (is_null($value)) {
return;
}

if (is_array($value) || ($value instanceof \ArrayAccess) || is_object($value)) {
foreach ($this->options['dataStructure'] as $key => $subValidator) {
if (is_array($value) || ($value instanceof \ArrayAccess)) {
if (array_key_exists($key, $value)) {
$subValue = $value[$key];
} else {
$subValue = null;
}
} elseif (ObjectAccess::isPropertyGettable($value, $key)) {
$subValue = ObjectAccess::getPropertyPath($value, $key);
} else {
$subValue = null;
}

if ($subValidator instanceof ValidatorInterface) {
$subResult = $subValidator->validate($subValue);
if ($subResult->hasErrors()) {
$this->addError('DataStructure-Property %s is not valid', 1515003533, [$key]);
}
}
}
} else {
$this->addError('DataStructure is expected to be an array or implement ArrayAccess', 1515070099);
}
}
}
11 changes: 11 additions & 0 deletions Classes/Validators/PropTypeValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class PropTypeValidator extends ConjunctionValidator implements ProtectedContext
'regex',
'arrayOf',
'shape',
'dataStructure',
'anyOf',
'oneOf'
];
Expand Down Expand Up @@ -151,6 +152,16 @@ public function shape($shape)
return $this;
}

/**
* @param array $arguments
* @return $this
*/
public function dataStructure($shape)
{
$this->addValidator(new DataStructureValidator(['dataStructure' => $shape]));
return $this;
}

/**
* @param string $type
* @return $this
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ via.
Validate an array was given and all validate with the given validator, accepts null.
* `PropTypes.anyOf( PropTypes.string, PropTypes.integer )`:
Validate the value validates at least with one of the given validators, accepts null..
* `PropTypes.dataStructure({'foo': PropTypes.integer, 'bar': PropTypes.string})`:
Validate the keys of the given array validate with the assigned Validator,
accepts null and ignores all other keys. The key validators have to define wether a single key is required.
* `PropTypes.shape({'foo': PropTypes.integer, 'bar': PropTypes.string})`:
Validate the keys of the given array validate with the assigned Validator,
accepts null and ignores all other keys. The key validators have to define wether a single key is required.
Expand Down
152 changes: 152 additions & 0 deletions Tests/Unit/Validators/DataStructureValidatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php
namespace PackageFactory\AtomicFusion\PropTypes\Tests\Unit\Validators;

use Neos\Flow\Tests\Unit\Validation\Validator\AbstractValidatorTestcase;
use PackageFactory\AtomicFusion\PropTypes\Validators\DataStructureValidator;
use Neos\Flow\Validation\Validator\ValidatorInterface;
use Neos\Error\Messages\Result;

/**
* Testcase for the shape validator
*
*/
class DataStructureValidatorTest extends AbstractValidatorTestcase
{

/**
* @var ValidatorInterface
*/
protected $mockItemValidator;


public function setUp()
{
$mockSuccessResult = $this->createMock(Result::class);
$mockSuccessResult->expects($this->any())->method('hasErrors')->will($this->returnValue(false));

$this->mockItemValidator = $this->createMock(ValidatorInterface::class);
$this->mockItemValidator
->expects($this->any())
->method('validate')
->will($this->returnValue($mockSuccessResult));

$this->validator = new DataStructureValidator(['dataStructure' => [
'foo' => $this->mockItemValidator,
'bar' => $this->mockItemValidator
]]);
}

/**
* @test
*/
public function validatorAcceptsNull()
{
$this->assertFalse($this->validator->validate(null)->hasErrors());
}

/**
* @test
*/
public function validatorAcceptsEmptyArray()
{
$this->assertFalse($this->validator->validate([])->hasErrors());
}

/**
* @test
*/
public function validatorAcceptsEmptyArrayObject()
{
$arrayObject = new \ArrayObject();
$this->assertFalse($this->validator->validate($arrayObject)->hasErrors());
}

/**
* @test
*/
public function validatorCallsItemValidatorForEachKeyOfShape()
{
$shape = ['foo' => 123, 'bar' => 'string'];

$this->mockItemValidator->expects($this->exactly(2))->method('validate');
$this->mockItemValidator->expects($this->at(0))->method('validate')->with(123);
$this->mockItemValidator->expects($this->at(1))->method('validate')->with('string');

$this->validator->validate($shape);
}

/**
* @test
*/
public function validatorCallsItemValidatorForEachKeyOfShapeOnArrayObjects()
{
$shape = new \ArrayObject(['foo' => 123, 'bar' => 'string']);

$this->mockItemValidator->expects($this->exactly(2))->method('validate');
$this->mockItemValidator->expects($this->at(0))->method('validate')->with(123);
$this->mockItemValidator->expects($this->at(1))->method('validate')->with('string');

$this->validator->validate($shape);
}


/**
* @test
*/
public function validatorCallsItemValidatorForEachKeyOfShapeOnStdClassObjects()
{
$shape = new \stdClass();
$shape->foo = 123;
$shape->bar = 'string';

$this->mockItemValidator->expects($this->exactly(2))->method('validate');
$this->mockItemValidator->expects($this->at(0))->method('validate')->with(123);
$this->mockItemValidator->expects($this->at(1))->method('validate')->with('string');

$this->validator->validate($shape);
}

/**
* @test
*/
public function validatorCallsItemValidatorForEachKeyOfShapeOfClassObjects()
{
$shape = new class() {
public function getFoo()
{
return 123;
}
public function getBar()
{
return 'string';
}
};

$this->mockItemValidator->expects($this->exactly(2))->method('validate');
$this->mockItemValidator->expects($this->at(0))->method('validate')->with(123);
$this->mockItemValidator->expects($this->at(1))->method('validate')->with('string');

$this->validator->validate($shape);
}

/**
* @test
*/
public function validatorReturnsErrorIfItemsDoNotValidate()
{
$shape = ['foo' => 123, 'bar' => 'string'];

$mockErrorResult = $this->createMock(Result::class);
$mockErrorResult->expects($this->any())->method('hasErrors')->will($this->returnValue(true));

$mockItemValidator = $this->createMock(ValidatorInterface::class);
$mockItemValidator->expects($this->any())->method('validate')->will($this->returnValue($mockErrorResult));

$validator = new DataStructureValidator(['dataStructure' => [
'foo' => $mockItemValidator,
'bar' => $mockItemValidator
]]);

$this->assertTrue($validator->validate($shape)->hasErrors());
}
}

0 comments on commit f9bf281

Please sign in to comment.