Skip to content

Commit

Permalink
improve dataset validation, add ConfirmationValidator (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomK authored Dec 8, 2021
1 parent cdb72b8 commit ab5027e
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 33 deletions.
26 changes: 14 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import {addValidator} from './js/validator';
import {EqualValidator} from './js/validators/EqualValidator';
import {NotEqualValidator} from './js/validators/NotEqualValidator';
import {EnumValidator} from './js/validators/EnumValidator';
import {ConstEnumValidator} from './js/validators/ConstEnumValidator';
import {BoolValidator} from './js/validators/BoolValidator';
import {StringValidator} from './js/validators/StringValidator';
import {RequiredValidator} from './js/validators/RequiredValidator';
import {MultiValidator} from './js/validators/MultiValidator';
import {RegexValidator} from './js/validators/RegexValidator';
import {EmailValidator} from './js/validators/EmailValidator';
import {IPv4Validator} from './js/validators/IPv4Validator';
import {addValidator} from './js/validator.js';
import {EqualValidator} from './js/validators/EqualValidator.js';
import {NotEqualValidator} from './js/validators/NotEqualValidator.js';
import {EnumValidator} from './js/validators/EnumValidator.js';
import {ConstEnumValidator} from './js/validators/ConstEnumValidator.js';
import {BoolValidator} from './js/validators/BoolValidator.js';
import {StringValidator} from './js/validators/StringValidator.js';
import {RequiredValidator} from './js/validators/RequiredValidator.js';
import {MultiValidator} from './js/validators/MultiValidator.js';
import {RegexValidator} from './js/validators/RegexValidator.js';
import {EmailValidator} from './js/validators/EmailValidator.js';
import {IPv4Validator} from './js/validators/IPv4Validator.js';
import {ConfirmationValidator} from './js/validators/ConfirmationValidator.js';

export * from './js/validator';

Expand All @@ -24,3 +25,4 @@ addValidator('Multi', MultiValidator);
addValidator('Regex', RegexValidator);
addValidator('Email', EmailValidator);
addValidator('IPv4', IPv4Validator);
addValidator('Confirmation', ConfirmationValidator);
21 changes: 21 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {IPv4Validator} from './js/validators/IPv4Validator';
import {NumberValidator} from './js/validators/NumberValidator';
import {IntegerValidator} from './js/validators/IntegerValidator';
import {DecimalValidator} from './js/validators/DecimalValidator';
import {ConfirmationValidator} from './js/validators/ConfirmationValidator.js';

function testSuccess(response)
{
Expand Down Expand Up @@ -303,3 +304,23 @@ test(
testFailure(v.validate('100000'), ['must be less than 150']);
}
);

test(
'ConfirmationValidator',
() =>
{
let v = new ConfirmationValidator('field2');
v.setData({'field1': 10});
testFailure(v.validate(v.getData()['field1']), ['value does not match']);
v.setData({'field2': ''});
testFailure(v.validate(v.getData()['field1']), ['value does not match']);
v.setData({'field1': 'yes', 'field2': 'no'});
testFailure(v.validate(v.getData()['field1']), ['value does not match']);
v.setData({'field1': 'no', 'field2': 'yes'});
testFailure(v.validate(v.getData()['field1']), ['value does not match']);
v.setData({'field1': 'test', 'field2': 'test'});
testSuccess(v.validate(v.getData()['field1']));
v.setData({'field1': 123, 'field2': 123});
testSuccess(v.validate(v.getData()['field1']));
}
);
15 changes: 15 additions & 0 deletions js/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ export class Validator
}
}

export class DataSetValidator extends Validator
{
_data = {};

setData(data)
{
this._data = data;
}

getData()
{
return this._data;
}
}

export class ValidationResponse
{
constructor(errors, potentiallyValid)
Expand Down
26 changes: 26 additions & 0 deletions js/validators/ConfirmationValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {DataSetValidator, ValidationResponse} from '../validator.js';

export class ConfirmationValidator extends DataSetValidator
{
constructor(field)
{
super();
this._field = field;
}

static deserialize(config)
{
return new this(config.field);
}

validate(value)
{
const data = this.getData();
const compare = data[this._field] ? data[this._field] : null;
if(compare !== value)
{
return ValidationResponse.error(['value does not match']);
}
return ValidationResponse.success();
}
}
19 changes: 19 additions & 0 deletions src/DatasetValidatorTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Packaged\Validate;

trait DatasetValidatorTrait
{
protected $_data = [];

public function setData(array $data): self
{
$this->_data = $data;
return $this;
}

public function getData(): array
{
return $this->_data;
}
}
3 changes: 3 additions & 0 deletions src/IDataSetValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@

interface IDataSetValidator
{
public function setData(array $data): self;

public function getData(): array;
}
41 changes: 41 additions & 0 deletions src/Validators/ConfirmationValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
namespace Packaged\Validate\Validators;

use Generator;
use Packaged\Validate\AbstractSerializableValidator;
use Packaged\Validate\DatasetValidatorTrait;
use Packaged\Validate\IDataSetValidator;
use Packaged\Validate\SerializableValidator;

class ConfirmationValidator extends AbstractSerializableValidator implements IDataSetValidator
{
use DatasetValidatorTrait;

protected $_field;

public function __construct($field)
{
$this->_field = $field;
}

public static function deserialize($configuration): SerializableValidator
{
return new static($configuration->field);
}

public function serialize(): array
{
return [
'field' => $this->_field,
];
}

protected function _doValidate($value): Generator
{
$compare = $this->_data[$this->_field] ?? null;
if($compare !== $value)
{
yield $this->_makeError('value does not match');
}
}
}
22 changes: 13 additions & 9 deletions src/Validators/RemoteValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use Generator;
use Packaged\Validate\AbstractSerializableValidator;
use Packaged\Validate\DatasetValidatorTrait;
use Packaged\Validate\IDataSetValidator;
use Packaged\Validate\IValidator;
use Packaged\Validate\SerializableValidator;
Expand All @@ -11,6 +12,8 @@

class RemoteValidator extends AbstractSerializableValidator implements IDataSetValidator
{
use DatasetValidatorTrait;

protected $_validators;
protected $_field;

Expand Down Expand Up @@ -39,17 +42,14 @@ public function serialize(): array
];
}

protected function _doValidate($values): Generator
protected function _doValidate($value): Generator
{
if(!is_array($values))
{
return $this->_makeError('not a valid value for a dataset validator');
}
$data = $this->getData();

$secondary = [];
foreach($this->_validators as $validator)
{
if($validator->remoteValidator->isValid($values[$validator->remoteField] ?? null))
if($validator->remoteValidator->isValid($data[$validator->remoteField] ?? null))
{
if(!isset($secondary[$this->_field]))
{
Expand All @@ -59,13 +59,17 @@ protected function _doValidate($values): Generator
}
}

$fieldValue = $values[$this->_field] ?? null;
foreach($secondary as $field => $validators)
foreach($secondary as $validators)
{
foreach($validators as $validator)
{
/** @var IValidator $validator */
foreach($validator->validate($fieldValue) as $error)
if($validator instanceof IDataSetValidator)
{
$validator->setData($this->getData());
}

foreach($validator->validate($value) as $error)
{
yield $error;
}
Expand Down
31 changes: 31 additions & 0 deletions tests/ConfirmationValidatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
namespace Packaged\Validate\Tests;

use Packaged\Validate\Validators\ConfirmationValidator;
use PHPUnit\Framework\TestCase;

class ConfirmationValidatorTest extends TestCase
{
/**
* @dataProvider provider
*/
public function testConfirmationValidator(array $data, bool $expect)
{
$validator = new ConfirmationValidator('field2');
$validator->setData($data);

$this->assertEquals($expect, $validator->isValid($data['field1'] ?? null));
}

public function provider()
{
return [
[['field1' => 10], false],
[['field2' => ''], false],
[['field1' => 'yes', 'field2' => 'no'], false],
[['field1' => 'no', 'field2' => 'yes'], false],
[['field1' => 'test', 'field2' => 'test'], true],
[['field1' => 123, 'field2' => 123], true],
];
}
}
30 changes: 18 additions & 12 deletions tests/RemoteValidatorTest.php
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
<?php
namespace Packaged\Validate\Tests;

use Packaged\Validate\ValidationException;
use Packaged\Validate\Validators\EqualValidator;
use Packaged\Validate\Validators\NumberValidator;
use Packaged\Validate\Validators\RemoteValidator;
use PHPUnit\Framework\TestCase;

class RemoteValidatorTest extends TestCase
{
public function testRemoteValidator()
/**
* @dataProvider provider
*/
public function testRemoteValidator(array $data, bool $expect)
{
$validator = new RemoteValidator('testField');
$validator->addValidator(new NumberValidator(0, 5), 'prereqField', new EqualValidator('abc'));
$this->assertTrue($validator->isValid([]));
$this->assertTrue($validator->isValid(['testField' => 10]));
$this->assertTrue($validator->isValid(['prereqField' => '']));
$this->assertTrue($validator->isValid(['prereqField' => '']));
$this->assertFalse($validator->isValid(['prereqField' => 'abc']));
$this->assertFalse($validator->isValid(['prereqField' => 'abc', 'testField' => 10]));
$this->assertTrue($validator->isValid(['prereqField' => 'abc', 'testField' => 3]));
$validator->setData($data);

$this->expectException(ValidationException::class);
$this->expectExceptionMessage('not a valid value for a dataset validator');
$validator->assert('invalid');
$this->assertEquals($expect, $validator->isValid($data['testField'] ?? null));
}

public function provider()
{
return [
[['testField' => 10], true],
[['prereqField' => ''], true],
[['prereqField' => ''], true],
[['prereqField' => 'abc'], false],
[['prereqField' => 'abc', 'testField' => 10], false],
[['prereqField' => 'abc', 'testField' => 3], true],
];
}
}

0 comments on commit ab5027e

Please sign in to comment.