diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5955a5a..54d12ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: [ 7.3, 7.4, 8.0 ] + php: [ 7.4, 8.0 ] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/index.test.js b/index.test.js index 8b8ad28..337776e 100644 --- a/index.test.js +++ b/index.test.js @@ -15,21 +15,18 @@ import {DecimalValidator} from './js/validators/DecimalValidator'; import {ConfirmationValidator} from './js/validators/ConfirmationValidator.js'; import {RegexValidator} from './js/validators/RegexValidator.js'; -function testSuccess(response) -{ +function testSuccess(response) { expect(response).toHaveProperty('errors', []); } -function testFailure(response, errors, potentiallyValid = false) -{ +function testFailure(response, errors, potentiallyValid = false) { expect(response).toHaveProperty('errors', errors); expect(response).toHaveProperty('potentiallyValid', potentiallyValid); } test( 'deserialize', - () => - { + () => { let v = Validator.fromJsonObject({t: 'String', c: {'minLength': 2, 'maxLength': 5}}); expect(v).toBeInstanceOf(StringValidator); expect(v._minLength).toStrictEqual(2); @@ -37,10 +34,50 @@ test( } ); +test( + 'TranslatedStringValidator', + () => { + let v = new StringValidator(2, 5); + v.dictionary = { + invalid: 'kein gültiger Wert', + min: 'muss mindestens sein %s characters', + max: 'darf nicht mehr als sein %s characters' + }; + + testFailure(v.validate(), ['kein gültiger Wert']); + testFailure(v.validate(''), ['muss mindestens sein 2 characters']); + testFailure(v.validate('testtest'), ['darf nicht mehr als sein 5 characters']); + } +); + +test( + 'MissinKeyTranslatedStringValidator', + () => { + let v = new StringValidator(2, 5); + v.dictionary = { + invalid: 'kein gültiger Wert', + min: 'muss mindestens sein %s characters' + // max is missing + }; + + testFailure(v.validate(), ['kein gültiger Wert']); + testFailure(v.validate(''), ['muss mindestens sein 2 characters']); + testFailure(v.validate('testesttest'), ['must be no more than 5 characters']); // default error message + } +) + +test( + 'TranslatedRequiredValidator', + () => { + let v = new RequiredValidator(); + v.dictionary = {invalid: 'kein gültiger Wert'}; + testFailure(v.validate(), ['kein gültiger Wert']); + } +); + test( 'StringValidator', - () => - { + () => { let v = new StringValidator(); testSuccess(v.validate('test')); testSuccess(v.validate('')); @@ -70,8 +107,7 @@ test( test( 'BoolValidator', - () => - { + () => { const v = new BoolValidator(); testFailure(v.validate('test'), ['Invalid boolean value']); testFailure(v.validate(''), ['Invalid boolean value']); @@ -90,8 +126,7 @@ test( test( 'EnumValidator', - () => - { + () => { let v = new EnumValidator(); testSuccess(v.validate('')); testFailure(v.validate('test'), ['not a valid value']); @@ -117,8 +152,7 @@ test( test( 'ConstEnumValidator', - () => - { + () => { let v = new ConstEnumValidator(); testSuccess(v.validate('')); testFailure(v.validate('test'), ['not a valid value']); @@ -144,8 +178,7 @@ test( test( 'EqualValidator', - () => - { + () => { let v = new EqualValidator('test'); testFailure(v.validate(''), ['value does not match']); testSuccess(v.validate('test')); @@ -157,8 +190,7 @@ test( test( 'NotEqualValidator', - () => - { + () => { let v = new NotEqualValidator('test'); testFailure(v.validate('test'), ['value must not match']); testSuccess(v.validate('')); @@ -170,8 +202,7 @@ test( test( 'RequiredValidator', - () => - { + () => { let v = new RequiredValidator(); testSuccess(v.validate(true)); testSuccess(v.validate(false)); @@ -189,8 +220,7 @@ test( test( 'EmailValidator', - () => - { + () => { let v = new EmailValidator(); testSuccess(v.validate('test@test.com')); testSuccess(v.validate('a@b.com')); @@ -202,8 +232,7 @@ test( test( 'IPv4Validator', - () => - { + () => { let v = new IPv4Validator(); testSuccess(v.validate('0.0.0.0')); testSuccess(v.validate('255.255.255.255')); @@ -222,8 +251,7 @@ test( test( 'NumberValidator', - () => - { + () => { let v = new NumberValidator(); testFailure(v.validate('test'), ['must be a number']); testSuccess(v.validate(1)); @@ -246,28 +274,26 @@ test( test( 'RegexValidator', - () => - { + () => { let v = new RegexValidator('not valid regex'); testFailure(v.validate('test'), ['not a valid regular expression']); v = new RegexValidator({}); testFailure(v.validate('test'), ['not a valid regular expression']); - v = new RegexValidator('/test/', 'not test'); - testFailure(v.validate('abc'), ['not test']); + v = new RegexValidator('/test/'); + testFailure(v.validate('abc'), ['not a valid regular expression']); v = new RegexValidator('/test/'); - testFailure(v.validate('abc'), ['does not match regular expression']); - testFailure(v.validate('1'), ['does not match regular expression']); + testFailure(v.validate('abc'), ['not a valid regular expression']); + testFailure(v.validate('1'), ['not a valid regular expression']); testSuccess(v.validate('test')); } ); test( 'IntegerValidator', - () => - { + () => { let v = new IntegerValidator(); testFailure(v.validate('test'), ['must be a number']); testSuccess(v.validate(1)); @@ -296,8 +322,7 @@ test( test( 'DecimalValidator', - () => - { + () => { let v = new DecimalValidator(); testFailure(v.validate('test'), ['must be a number']); testSuccess(v.validate(1)); @@ -328,8 +353,7 @@ test( test( 'ConfirmationValidator', - () => - { + () => { let v = new ConfirmationValidator('field2'); v.setData({'field1': 10}); testFailure(v.validate(v.getData()['field1']), ['value does not match']); diff --git a/js/validator.js b/js/validator.js index 7e3c4e2..d49be5f 100644 --- a/js/validator.js +++ b/js/validator.js @@ -1,5 +1,6 @@ /** - * @typedef {function(new: Validator), deserialize} ValidatorType + * @typedef {function(new: Validator)} ValidatorType + * @property deserialize */ /** @@ -8,24 +9,22 @@ */ const _validatorMap = new Map(); -export class Validator -{ +export class Validator { /** * @param {Object} obj * @return {Validator} */ - static fromJsonObject(obj) - { - if(!_validatorMap.has(obj.t)) - { + static fromJsonObject(obj) { + if(!_validatorMap.has(obj.t)) { throw 'unrecognised type ' + obj.t; } - const c = _validatorMap.get(obj.t); - return c.deserialize(obj.c); + const validator = _validatorMap.get(obj.t); + const initializedValidator = validator.deserialize(obj.c); + initializedValidator.dictionary = obj.d; + return initializedValidator; } - static deserialize(config) - { + static deserialize(config) { return new this(); } @@ -34,70 +33,63 @@ export class Validator * @return {ValidationResponse} * @throws */ - validate(value) - { + validate(value) { throw 'validate not implemented on ' + this.constructor.name; } + + /** + * @param {Object} dictionary + */ + set dictionary(dictionary) { + this._dictionary = dictionary || {}; + } } -export class DataSetValidator extends Validator -{ +export class DataSetValidator extends Validator { _data = {}; - setData(data) - { + setData(data) { this._data = data; } - getData() - { + getData() { return this._data; } } -export class ValidationResponse -{ - constructor(errors, potentiallyValid) - { +export class ValidationResponse { + constructor(errors, potentiallyValid) { this._errors = errors || []; this._potentiallyValid = potentiallyValid || false; } - get errors() - { + get errors() { return this._errors; } - get potentiallyValid() - { + get potentiallyValid() { return this._potentiallyValid; } - static success() - { + static success() { return new ValidationResponse([], true); } - static potentiallyValid(errors = []) - { + static potentiallyValid(errors = []) { return new ValidationResponse(errors, true); } - static error(errors = []) - { + static error(errors = []) { return new ValidationResponse(errors, false); } /** * @param {...ValidationResponse} responses */ - combine(...responses) - { + combine(...responses) { responses.forEach( - r => - { - if(r instanceof ValidationResponse) - { + r => { + if(r instanceof ValidationResponse) { this._errors.push(...r._errors); this._potentiallyValid = this._potentiallyValid && r._potentiallyValid; } @@ -110,7 +102,6 @@ export class ValidationResponse * @param {string} name * @param {ValidatorType} validator */ -export function addValidator(name, validator) -{ +export function addValidator(name, validator) { _validatorMap.set(name, validator); } diff --git a/js/validators/BoolValidator.js b/js/validators/BoolValidator.js index e58f65f..430668e 100644 --- a/js/validators/BoolValidator.js +++ b/js/validators/BoolValidator.js @@ -1,27 +1,28 @@ import {ValidationResponse, Validator} from '../validator'; -export class BoolValidator extends Validator -{ - validate(value) - { - if(value === null || value === undefined) - { +export class BoolValidator extends Validator { + validate(value) { + if(value === null || value === undefined) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['Invalid boolean value']); } - if(typeof value !== 'boolean') - { - if(typeof value === 'string') - { - if(!(/true|false|0|1/.test(value.toLowerCase()))) - { + if(typeof value !== 'boolean') { + if(typeof value === 'string') { + if(!(/true|false|0|1/.test(value.toLowerCase()))) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['Invalid boolean value']); } } - else if(typeof value === 'number') - { - if(value !== 0 && value !== 1) - { + else if(typeof value === 'number') { + if(value !== 0 && value !== 1) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['Invalid boolean value']); } } diff --git a/js/validators/ConfirmationValidator.js b/js/validators/ConfirmationValidator.js index dc29957..5750aff 100644 --- a/js/validators/ConfirmationValidator.js +++ b/js/validators/ConfirmationValidator.js @@ -1,28 +1,28 @@ import {DataSetValidator, ValidationResponse} from '../validator.js'; -export class ConfirmationValidator extends DataSetValidator -{ - constructor(field) - { +export class ConfirmationValidator extends DataSetValidator { + constructor(field) { super(); this._field = field; } - static deserialize(config) - { + static deserialize(config) { return new this(config.field); } - validate(value) - { + validate(value) { const data = this.getData(); const compare = data.hasOwnProperty(this._field) ? data[this._field] : null; - if(compare !== value) - { - if(compare.substr(0, value.length) === value) - { + if(compare !== value) { + if(compare?.substr(0, value?.length) === value) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.potentiallyValid([this._dictionary.invalid]); + } return ValidationResponse.potentiallyValid(['value does not match']); } + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['value does not match']); } return ValidationResponse.success(); diff --git a/js/validators/DecimalValidator.js b/js/validators/DecimalValidator.js index 3d80d60..310b1ff 100644 --- a/js/validators/DecimalValidator.js +++ b/js/validators/DecimalValidator.js @@ -1,13 +1,10 @@ import {ValidationResponse} from '../validator'; import {NumberValidator} from './NumberValidator'; -export class DecimalValidator extends NumberValidator -{ - constructor(decimalPlaces = null, minValue = null, maxValue = null) - { +export class DecimalValidator extends NumberValidator { + constructor(decimalPlaces = null, minValue = null, maxValue = null) { super(); - if((maxValue !== null) && (minValue !== null) && (maxValue < minValue)) - { + if((maxValue !== null) && (minValue !== null) && (maxValue < minValue)) { throw 'maxValue must be greater than or equal to minValue'; } this._decimalPlaces = decimalPlaces; @@ -15,22 +12,24 @@ export class DecimalValidator extends NumberValidator this._maxValue = maxValue; } - static deserialize(config) - { + static deserialize(config) { return new this(config.decimalPlaces, config.minValue, config.maxValue); } - validate(value) - { + validate(value) { const response = super.validate(value); const split = value.toString().split('.'); - if(split.length > 2) - { + if(split.length > 2) { + if(this._dictionary && this._dictionary.invalid) { + response.combine(ValidationResponse.error([this._dictionary.invalid])); + } response.combine(ValidationResponse.error(['invalid decimal value'])); } - else if(split.length === 2 && (this._decimalPlaces !== null && split[1].length > this._decimalPlaces)) - { + else if(split.length === 2 && (this._decimalPlaces !== null && split[1].length > this._decimalPlaces)) { + if(this._dictionary && this._dictionary.invalid) { + response.combine(ValidationResponse.error([this._dictionary.invalid.replace('%s', this._decimalPlaces.toString())])); + } response.combine(ValidationResponse.error([`must be a number to no more than ${this._decimalPlaces} decimal places`])); } return response; diff --git a/js/validators/EmailValidator.js b/js/validators/EmailValidator.js index 2a0622a..f9994e3 100644 --- a/js/validators/EmailValidator.js +++ b/js/validators/EmailValidator.js @@ -1,17 +1,13 @@ import {RegexValidator} from './RegexValidator'; -export class EmailValidator extends RegexValidator -{ - constructor(message = 'invalid email address') - { +export class EmailValidator extends RegexValidator { + constructor() { super( - /^[_a-zA-Z0-9+\-]+(\.[_a-zA-Z0-9+\-]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(\.[a-zA-Z]{2,})$/, - message + /^[_a-zA-Z0-9+\-]+(\.[_a-zA-Z0-9+\-]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(\.[a-zA-Z]{2,})$/ ); } - static deserialize(config) - { - return new this(config.message); + getDefaultErrorMessage() { + return 'invalid email address'; } } diff --git a/js/validators/EnumValidator.js b/js/validators/EnumValidator.js index 0b1c707..b7d5b0b 100644 --- a/js/validators/EnumValidator.js +++ b/js/validators/EnumValidator.js @@ -1,32 +1,31 @@ import {ValidationResponse, Validator} from '../validator'; -export class EnumValidator extends Validator -{ - constructor(allowedValues = [], caseSensitive = false, negate = false) - { +export class EnumValidator extends Validator { + constructor(allowedValues = [], caseSensitive = false, negate = false) { super(); this._allowedValues = allowedValues || []; this._caseSensitive = !!caseSensitive; this._negate = !!negate; } - static deserialize(config) - { + static deserialize(config) { return new this(config.allowedValues || [], !!config.caseSensitive, !!config.negate); } - validate(value) - { - if(this._allowedValues.length) - { + validate(value) { + if(this._allowedValues.length) { const regex = new RegExp(this._allowedValues.join('|'), !!this._caseSensitive ? '' : 'i'); - if(this._negate ^ !regex.test(value)) - { + if(this._negate ^ !regex.test(value)) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['not a valid value']); } } - else if(this._negate ^ (value !== null && value !== '')) - { + else if(this._negate ^ (value !== null && value !== '')) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['not a valid value']); } return ValidationResponse.success(); diff --git a/js/validators/EqualValidator.js b/js/validators/EqualValidator.js index f4358f7..f279558 100644 --- a/js/validators/EqualValidator.js +++ b/js/validators/EqualValidator.js @@ -17,6 +17,10 @@ export class EqualValidator extends Validator { if(value !== this._expect) { + if(this._dictionary && this._dictionary.invalid) + { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['value does not match']); } return ValidationResponse.success(); diff --git a/js/validators/FileSizeValidator.js b/js/validators/FileSizeValidator.js index 0ee070c..6780801 100644 --- a/js/validators/FileSizeValidator.js +++ b/js/validators/FileSizeValidator.js @@ -1,26 +1,23 @@ import {ValidationResponse, Validator} from '../validator'; -export class FileSizeValidator extends Validator -{ - constructor(maxSize = null) - { +export class FileSizeValidator extends Validator { + constructor(maxSize = null) { super(); - if(maxSize === null) - { + if(maxSize === null) { throw 'maxSize must be set'; } this._maxSize = maxSize; } - static deserialize(config) - { + static deserialize(config) { return new this(config.maxSize); } - validate(value) - { - if('size' in value && value.size > (this._maxSize * 1024 * 1024)) - { + validate(value) { + if('size' in value && value.size > (this._maxSize * 1024 * 1024)) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid.replace('%s', this._maxSize.toString())]); + } return ValidationResponse.error(['File upload cannot be more than ' + this._maxSize + 'mb in size']); } diff --git a/js/validators/IPv4Validator.js b/js/validators/IPv4Validator.js index cfd6907..56c1d83 100644 --- a/js/validators/IPv4Validator.js +++ b/js/validators/IPv4Validator.js @@ -1,17 +1,13 @@ import {RegexValidator} from './RegexValidator'; -export class IPv4Validator extends RegexValidator -{ - constructor(message = 'invalid IPv4 address') - { +export class IPv4Validator extends RegexValidator { + constructor() { super( - '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', - message + '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/' ); } - static deserialize(config) - { - return new this(config.message); + getDefaultErrorMessage() { + return 'invalid IPv4 address'; } } diff --git a/js/validators/IntegerValidator.js b/js/validators/IntegerValidator.js index 85a430b..8bc28b7 100644 --- a/js/validators/IntegerValidator.js +++ b/js/validators/IntegerValidator.js @@ -1,13 +1,13 @@ import {ValidationResponse} from '../validator'; import {NumberValidator} from './NumberValidator'; -export class IntegerValidator extends NumberValidator -{ - validate(value) - { +export class IntegerValidator extends NumberValidator { + validate(value) { const response = super.validate(value); - if(response.errors.length === 0 && parseInt(value).toString() !== value.toString()) - { + if(response.errors.length === 0 && parseInt(value).toString() !== value.toString()) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } response.combine(ValidationResponse.error(['must be an integer'])); } return response; diff --git a/js/validators/NotEqualValidator.js b/js/validators/NotEqualValidator.js index df78150..6ffb5b6 100644 --- a/js/validators/NotEqualValidator.js +++ b/js/validators/NotEqualValidator.js @@ -1,22 +1,20 @@ import {ValidationResponse, Validator} from '../validator'; -export class NotEqualValidator extends Validator -{ - constructor(expect) - { +export class NotEqualValidator extends Validator { + constructor(expect) { super(); this._expect = expect; } - static deserialize(config) - { + static deserialize(config) { return new this(config.expect); } - validate(value) - { - if(value === this._expect) - { + validate(value) { + if(value === this._expect) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['value must not match']); } return ValidationResponse.success(); diff --git a/js/validators/NumberValidator.js b/js/validators/NumberValidator.js index 9b57e1d..c93fc9e 100644 --- a/js/validators/NumberValidator.js +++ b/js/validators/NumberValidator.js @@ -1,35 +1,36 @@ import {ValidationResponse, Validator} from '../validator'; -export class NumberValidator extends Validator -{ - constructor(minValue = null, maxValue = null) - { +export class NumberValidator extends Validator { + constructor(minValue = null, maxValue = null) { super(); - if((maxValue !== null) && (minValue !== null) && (maxValue < minValue)) - { + if((maxValue !== null) && (minValue !== null) && (maxValue < minValue)) { throw 'maxValue must be greater than or equal to minValue'; } this._minValue = minValue; this._maxValue = maxValue; } - static deserialize(config) - { + static deserialize(config) { return new this(config.minValue, config.maxValue); } - validate(value) - { - if(!/^[0-9.]+$/.test(value)) - { + validate(value) { + if(!/^[0-9.]+$/.test(value)) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['must be a number']); } - else if((this._minValue !== null) && (value < this._minValue)) - { + else if((this._minValue !== null) && (value < this._minValue)) { + if(this._dictionary && this._dictionary.min) { + return ValidationResponse.error([this._dictionary.min.replace('%s', this._minValue.toString())]); + } return ValidationResponse.potentiallyValid([`must be more than ${this._minValue}`]); } - else if((this._maxValue !== null) && (value > this._maxValue)) - { + else if((this._maxValue !== null) && (value > this._maxValue)) { + if(this._dictionary && this._dictionary.max) { + return ValidationResponse.error([this._dictionary.max.replace('%s', this._maxValue.toString())]); + } return ValidationResponse.error([`must be less than ${this._maxValue}`]); } diff --git a/js/validators/RegexValidator.js b/js/validators/RegexValidator.js index 5c44d9c..f6a420b 100644 --- a/js/validators/RegexValidator.js +++ b/js/validators/RegexValidator.js @@ -1,41 +1,45 @@ import {ValidationResponse, Validator} from '../validator'; -export class RegexValidator extends Validator -{ - constructor(pattern, message = 'does not match regular expression') - { +export class RegexValidator extends Validator { + constructor(pattern) { super(); this._pattern = pattern; - this._message = message; } - static deserialize(config) - { - return new this(config.pattern, config.message); + static deserialize(config) { + return new this(config.pattern); } - validate(value) - { + validate(value) { let regex = this._pattern; - if(typeof regex === 'string') - { + if(typeof regex === 'string') { const parts = /\/(.*)\/(.*)/.exec(regex); - if(!parts) - { - return ValidationResponse.error(['not a valid regular expression']); + if(!parts) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } + return ValidationResponse.error([this.getDefaultErrorMessage()]); } regex = new RegExp(parts[1], parts[2]); } - if(!(regex instanceof RegExp)) - { - return ValidationResponse.error(['not a valid regular expression']); + if(!(regex instanceof RegExp)) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } + return ValidationResponse.error([this.getDefaultErrorMessage()]); } - if(!regex.test(value)) - { - return ValidationResponse.error([this._message]); + if(!regex.test(value)) { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } + return ValidationResponse.error([this.getDefaultErrorMessage()]); } return ValidationResponse.success(); } + + getDefaultErrorMessage() { + return 'not a valid regular expression'; + } } diff --git a/js/validators/RequiredValidator.js b/js/validators/RequiredValidator.js index a5af2bb..d15d9d4 100644 --- a/js/validators/RequiredValidator.js +++ b/js/validators/RequiredValidator.js @@ -1,11 +1,11 @@ import {ValidationResponse, Validator} from '../validator'; -export class RequiredValidator extends Validator -{ - validate(value) - { - if(value === undefined || value === null || value === '') - { +export class RequiredValidator extends Validator { + validate(value) { + if(value === undefined || value === null || value === '') { + if(this._dictionary && this._dictionary.invalid) { + return ValidationResponse.error([this._dictionary.invalid]); + } return ValidationResponse.error(['required']); } return ValidationResponse.success(); diff --git a/js/validators/StringValidator.js b/js/validators/StringValidator.js index c5ac6cf..c00b65c 100644 --- a/js/validators/StringValidator.js +++ b/js/validators/StringValidator.js @@ -18,16 +18,29 @@ export class StringValidator extends Validator { if(typeof value !== 'string') { + if(this._dictionary && this._dictionary.invalid) + { + return ValidationResponse.error([this._dictionary.invalid]); + } + return ValidationResponse.error(['not a valid value']); } if(this._minLength > 0 && value.length < this._minLength) { + if(this._dictionary && this._dictionary.min) + { + return ValidationResponse.error([this._dictionary.min.replace('%s', this._minLength.toString())]); + } return ValidationResponse.potentiallyValid(['must be at least ' + this._minLength + ' characters']); } if(this._maxLength > 0 && value.length > this._maxLength) { + if(this._dictionary && this._dictionary.max) + { + return ValidationResponse.error([this._dictionary.max.replace('%s', this._maxLength.toString())]); + } return ValidationResponse.error(['must be no more than ' + this._maxLength + ' characters']); } diff --git a/src/AbstractSerializableValidator.php b/src/AbstractSerializableValidator.php index 3528e9b..c063bac 100644 --- a/src/AbstractSerializableValidator.php +++ b/src/AbstractSerializableValidator.php @@ -14,6 +14,7 @@ final public function jsonSerialize(): array return [ 't' => $this::serializeType(), 'c' => $this->serialize(), + 'd' => $this->getDictionary(), ]; } } diff --git a/src/AbstractValidator.php b/src/AbstractValidator.php index cf57c17..55d35c4 100644 --- a/src/AbstractValidator.php +++ b/src/AbstractValidator.php @@ -6,6 +6,8 @@ abstract class AbstractValidator implements IValidator, JsonSerializable { + protected $_dictionary = []; + protected function _makeError(string $message): ValidationException { return new ValidationException($message); @@ -18,6 +20,26 @@ protected function _makeError(string $message): ValidationException */ abstract protected function _doValidate($value): Generator; + public static function withDictionary(array $dictionary, ...$args) + { + $validator = new static(...$args); + $validator->setDictionary($dictionary); + return $validator; + } + + public function getDictionary(): array + { + return $this->_dictionary; + } + + /** + * @param array $dictionary + */ + public function setDictionary(array $dictionary): void + { + $this->_dictionary = array_merge($this->_dictionary, $dictionary); + } + public function validate($value): array { $errors = []; diff --git a/src/SerializableValidator.php b/src/SerializableValidator.php index b1f028b..d444867 100644 --- a/src/SerializableValidator.php +++ b/src/SerializableValidator.php @@ -8,4 +8,6 @@ public static function serializeType(): string; public static function deserialize($configuration): SerializableValidator; public function serialize(): array; + + public function getDictionary(): array; } diff --git a/src/Validation.php b/src/Validation.php index aa11835..603c9ef 100644 --- a/src/Validation.php +++ b/src/Validation.php @@ -65,13 +65,22 @@ public static function fromJsonObject(\stdClass $o): ?SerializableValidator $classAlias = $o->t ?? null; /** @var object $classConfiguration */ $classConfiguration = $o->c ?? null; + $dictionary = $o->d ?? null; + if(isset(static::$_validators[$classAlias])) { /** @var SerializableValidator $class */ $class = static::$_validators[$classAlias]; if(is_subclass_of($class, SerializableValidator::class)) { - return $class::deserialize($classConfiguration); + $class = $class::deserialize($classConfiguration); + + if($class instanceof AbstractValidator) + { + $class->setDictionary(json_decode(json_encode($dictionary), true)); + } + + return $class; } } return null; diff --git a/src/Validators/ArrayKeysValidator.php b/src/Validators/ArrayKeysValidator.php index 3070cd1..843a902 100644 --- a/src/Validators/ArrayKeysValidator.php +++ b/src/Validators/ArrayKeysValidator.php @@ -7,6 +7,16 @@ class ArrayKeysValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; + public const DICT_MISSING = 'missing'; + public const DICT_UNKNOWN = 'unknown'; + + protected $_dictionary = [ + self::DICT_INVALID => 'must be an array', + self::DICT_MISSING => 'missing required entries: %s', + self::DICT_UNKNOWN => 'unknown entries: %s', + ]; + protected $_requiredEntries; protected $_allowUnknownEntries; @@ -38,7 +48,7 @@ protected function _doValidate($value): Generator { if(!is_array($value)) { - return $this->_makeError('must be an array'); + return $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } $valueKeys = array_keys($value); @@ -48,7 +58,8 @@ protected function _doValidate($value): Generator $missingEntries = array_diff($this->_requiredEntries, $valueKeys); if(count($missingEntries) > 0) { - yield $this->_makeError('missing entries: ' . implode(', ', $missingEntries)); + $err = sprintf($this->getDictionary()[self::DICT_MISSING], implode(', ', $missingEntries)); + yield $this->_makeError($err); } } @@ -57,7 +68,8 @@ protected function _doValidate($value): Generator $extraEntries = array_diff($valueKeys, $this->_requiredEntries); if(count($extraEntries) > 0) { - yield $this->_makeError('unknown entries: ' . implode(', ', $extraEntries)); + $err = sprintf($this->getDictionary()[self::DICT_UNKNOWN], implode(', ', $extraEntries)); + yield $this->_makeError($err); } } } diff --git a/src/Validators/ArrayValidator.php b/src/Validators/ArrayValidator.php index da0e8bd..4d7f309 100644 --- a/src/Validators/ArrayValidator.php +++ b/src/Validators/ArrayValidator.php @@ -9,6 +9,16 @@ class ArrayValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; + public const DICT_MIN = 'min'; + public const DICT_MAX = 'max'; + + protected $_dictionary = [ + self::DICT_INVALID => 'must be an array', + self::DICT_MIN => 'must contain at least %s items', + self::DICT_MAX => 'must not contain more than %s items', + ]; + protected $_validator; protected $_minCount; protected $_maxCount; @@ -42,18 +52,20 @@ protected function _doValidate($value): Generator { if(!is_array($value)) { - return $this->_makeError('must be an array'); + return $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } $numItems = count($value); if($numItems < $this->_minCount) { - return $this->_makeError('must contain at least ' . $this->_minCount . ' items'); + $err = str_replace('%s', $this->_minCount, $this->getDictionary()[self::DICT_MIN]); + return $this->_makeError($err); } if(($this->_maxCount > 0) && ($numItems > $this->_maxCount)) { - return $this->_makeError('must not contain more than ' . $this->_maxCount . ' items'); + $err = str_replace('%s', $this->_maxCount, $this->getDictionary()[self::DICT_MAX]); + return $this->_makeError($err); } foreach($value as $idx => $entry) @@ -80,5 +92,4 @@ public function getMaxCount(): int { return $this->_maxCount; } - } diff --git a/src/Validators/BoolValidator.php b/src/Validators/BoolValidator.php index d5a422e..305347e 100644 --- a/src/Validators/BoolValidator.php +++ b/src/Validators/BoolValidator.php @@ -7,6 +7,12 @@ class BoolValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; + + protected $_dictionary = [ + self::DICT_INVALID => 'Invalid boolean value', + ]; + public static function deserialize($configuration): SerializableValidator { return new static(); @@ -33,7 +39,7 @@ protected function _doValidate($value): Generator } if(!$result) { - yield $this->_makeError('Invalid boolean value'); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } } } diff --git a/src/Validators/ConfirmationValidator.php b/src/Validators/ConfirmationValidator.php index b9cabc6..34b7052 100644 --- a/src/Validators/ConfirmationValidator.php +++ b/src/Validators/ConfirmationValidator.php @@ -9,6 +9,12 @@ class ConfirmationValidator extends AbstractSerializableValidator implements IDataSetValidator { + public const DICT_INVALID = 'invalid'; + + protected $_dictionary = [ + self::DICT_INVALID => 'value does not match', + ]; + use DatasetValidatorTrait; protected $_field; @@ -35,7 +41,7 @@ protected function _doValidate($value): Generator $compare = $this->_data[$this->_field] ?? null; if($compare !== $value) { - yield $this->_makeError('value does not match'); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } } } diff --git a/src/Validators/DecimalValidator.php b/src/Validators/DecimalValidator.php index d54bc23..2644bad 100644 --- a/src/Validators/DecimalValidator.php +++ b/src/Validators/DecimalValidator.php @@ -6,6 +6,8 @@ class DecimalValidator extends NumberValidator { + public const DICT_DECIMAL = 'decimal'; + protected $_decimalPlaces; /** @@ -21,6 +23,8 @@ public function __construct( { parent::__construct($minValue, $maxValue); $this->_decimalPlaces = $decimalPlaces; + $this->_dictionary[self::DICT_INVALID] = 'invalid decimal value'; + $this->_dictionary[self::DICT_DECIMAL] = 'must be a decimal number with no more than %s decimal places'; } public static function deserialize($configuration): SerializableValidator @@ -51,11 +55,11 @@ protected function _doValidate($value): Generator $parts = explode('.', $value); if(count($parts) > 2) { - yield $this->_makeError('invalid decimal value'); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } else if(count($parts) == 2 && ($this->_decimalPlaces !== null && strlen($parts[1]) > $this->_decimalPlaces)) { - yield $this->_makeError('must be a number to no more than ' . $this->_decimalPlaces . ' decimal places'); + yield $this->_makeError($this->getDictionary()[self::DICT_DECIMAL]); } } } @@ -67,5 +71,4 @@ public function getDecimalPlaces(): int { return $this->_decimalPlaces; } - } diff --git a/src/Validators/EmailValidator.php b/src/Validators/EmailValidator.php index 76ae9e1..67cab8e 100644 --- a/src/Validators/EmailValidator.php +++ b/src/Validators/EmailValidator.php @@ -5,12 +5,12 @@ class EmailValidator extends RegexValidator { - public function __construct($message = 'invalid email address') + public function __construct() { parent::__construct( '/^[_a-zA-Z0-9+\-]+(\.[_a-zA-Z0-9+\-]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(\.[a-zA-Z]{2,})$/', - $message ); + $this->_dictionary[self::DICT_INVALID] = 'invalid email address'; } public static function deserialize($configuration): SerializableValidator diff --git a/src/Validators/EnumValidator.php b/src/Validators/EnumValidator.php index 40d6040..8ba6607 100644 --- a/src/Validators/EnumValidator.php +++ b/src/Validators/EnumValidator.php @@ -7,6 +7,12 @@ class EnumValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; + + protected $_dictionary = [ + self::DICT_INVALID => 'not a valid value', + ]; + protected $_allowedValues; protected $_caseSensitive; protected $_negate; @@ -52,7 +58,7 @@ protected function _doValidate($value): Generator { if($this->_negate xor ($value !== null && $value !== '')) { - return $this->_makeError('not a valid value'); + return $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } return null; } @@ -61,7 +67,7 @@ protected function _doValidate($value): Generator { if($this->_negate xor !in_array($value, $this->_getAllowedValues())) { - return $this->_makeError('not a valid value'); + return $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } return null; } @@ -77,7 +83,7 @@ protected function _doValidate($value): Generator } if($this->_negate xor !$result) { - yield $this->_makeError('not a valid value'); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } } @@ -95,5 +101,4 @@ public function isCaseSensitive(): bool { return $this->_caseSensitive; } - } diff --git a/src/Validators/EqualValidator.php b/src/Validators/EqualValidator.php index cec7bda..5930e16 100644 --- a/src/Validators/EqualValidator.php +++ b/src/Validators/EqualValidator.php @@ -7,6 +7,12 @@ class EqualValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; + + protected $_dictionary = [ + self::DICT_INVALID => 'value must match', + ]; + protected $_expect; public function __construct($expect) @@ -18,7 +24,7 @@ protected function _doValidate($value): Generator { if($value !== $this->_expect) { - yield $this->_makeError('value does not match'); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } } @@ -33,5 +39,4 @@ public function serialize(): array 'expect' => $this->_expect, ]; } - } diff --git a/src/Validators/FileSizeValidator.php b/src/Validators/FileSizeValidator.php index 8f108f7..aecef3f 100644 --- a/src/Validators/FileSizeValidator.php +++ b/src/Validators/FileSizeValidator.php @@ -7,6 +7,12 @@ class FileSizeValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; + + protected $_dictionary = [ + self::DICT_INVALID => 'File upload cannot be more than %smb in size', + ]; + protected $_maxSize; public function __construct($maxSize = null) @@ -38,7 +44,8 @@ protected function _doValidate($value): Generator // Validation if(is_array($value) && array_key_exists('size', $value) && $value['size'] > ($this->_maxSize * 1024 * 1024)) { - yield $this->_makeError("File upload cannot be more than " . $this->_maxSize . "mb in size"); + $err = str_replace('%s', $this->_maxSize, $this->getDictionary()[self::DICT_INVALID]); + yield $this->_makeError($err); } } @@ -46,5 +53,4 @@ public function getMaxSize() { return $this->_maxSize; } - } diff --git a/src/Validators/FunctionValidator.php b/src/Validators/FunctionValidator.php index 6bab42d..c8b9d75 100644 --- a/src/Validators/FunctionValidator.php +++ b/src/Validators/FunctionValidator.php @@ -8,6 +8,12 @@ class FunctionValidator extends AbstractValidator { + public const DICT_INVALID = 'invalid'; + + protected $_dictionary = [ + self::DICT_INVALID => 'Failed to validate', + ]; + /** * @var callable */ @@ -43,7 +49,7 @@ protected function _doValidate($value): Generator } else if(is_bool($result) && !$result) { - yield new ValidationException('Failed to validate'); + yield new ValidationException($this->getDictionary()[self::DICT_INVALID]); } } } diff --git a/src/Validators/IPv4AddressValidator.php b/src/Validators/IPv4AddressValidator.php index 9a194ac..38f866c 100644 --- a/src/Validators/IPv4AddressValidator.php +++ b/src/Validators/IPv4AddressValidator.php @@ -9,8 +9,10 @@ public function __construct() { parent::__construct( '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', - 'invalid IPv4 address' ); + $this->_dictionary = [ + self::DICT_INVALID => 'invalid IPv4 address', + ]; } public static function deserialize($configuration): SerializableValidator diff --git a/src/Validators/IntegerValidator.php b/src/Validators/IntegerValidator.php index 37db072..70d4725 100644 --- a/src/Validators/IntegerValidator.php +++ b/src/Validators/IntegerValidator.php @@ -5,6 +5,12 @@ class IntegerValidator extends NumberValidator { + public function __construct($minValue = null, $maxValue = null) + { + parent::__construct($minValue, $maxValue); + $this->_dictionary[self::DICT_INVALID] = 'must be an integer'; + } + protected function _doValidate($value): Generator { $passParent = true; @@ -17,7 +23,7 @@ protected function _doValidate($value): Generator { if(floor($value) != $value) { - yield $this->_makeError('must be an integer'); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } } } diff --git a/src/Validators/NotEqualValidator.php b/src/Validators/NotEqualValidator.php index 21fba08..a61ee3e 100644 --- a/src/Validators/NotEqualValidator.php +++ b/src/Validators/NotEqualValidator.php @@ -7,6 +7,12 @@ class NotEqualValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'inv'; + + protected $_dictionary = [ + self::DICT_INVALID => 'value must not match', + ]; + protected $_expect; public function __construct($expect) @@ -18,7 +24,7 @@ protected function _doValidate($value): Generator { if($value === $this->_expect) { - yield $this->_makeError('value must not match'); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } } diff --git a/src/Validators/NumberValidator.php b/src/Validators/NumberValidator.php index da8f1bb..182ef43 100644 --- a/src/Validators/NumberValidator.php +++ b/src/Validators/NumberValidator.php @@ -7,6 +7,16 @@ class NumberValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; + public const DICT_MIN = 'min'; + public const DICT_MAX = 'max'; + + protected $_dictionary = [ + self::DICT_INVALID => 'must be a number', + self::DICT_MIN => 'must be more than %s', + self::DICT_MAX => 'must be less than %s', + ]; + protected $_minValue; protected $_maxValue; @@ -39,15 +49,17 @@ protected function _doValidate($value): Generator { if(!is_numeric($value)) { - yield $this->_makeError('must be a number'); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } else if(($this->_minValue !== null) && ($value < $this->_minValue)) { - yield $this->_makeError('must be more than ' . $this->_minValue); + $err = str_replace('%s', $this->_minValue, $this->getDictionary()[self::DICT_MIN]); + yield $this->_makeError($err); } else if(($this->_maxValue !== null) && ($value > $this->_maxValue)) { - yield $this->_makeError('must be less than ' . $this->_maxValue); + $err = str_replace('%s', $this->_maxValue, $this->getDictionary()[self::DICT_MAX]); + yield $this->_makeError($err); } } @@ -66,5 +78,4 @@ public function getMaxValue() { return $this->_maxValue; } - } diff --git a/src/Validators/PropertiesValidator.php b/src/Validators/PropertiesValidator.php index cf71e81..ae36d4d 100644 --- a/src/Validators/PropertiesValidator.php +++ b/src/Validators/PropertiesValidator.php @@ -5,11 +5,17 @@ class PropertiesValidator extends ArrayKeysValidator { + public function __construct(array $requiredEntries, bool $allowUnknownEntries = false) + { + parent::__construct($requiredEntries, $allowUnknownEntries); + $this->_dictionary[self::DICT_INVALID] = 'must be an object'; + } + protected function _doValidate($value): Generator { if(!is_object($value)) { - return $this->_makeError('must be an object'); + return $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } foreach(parent::_doValidate(get_object_vars($value)) as $error) diff --git a/src/Validators/RegexValidator.php b/src/Validators/RegexValidator.php index e054da5..2cecca5 100644 --- a/src/Validators/RegexValidator.php +++ b/src/Validators/RegexValidator.php @@ -7,29 +7,31 @@ class RegexValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; protected $_pattern; protected $_message; + protected $_dictionary = [ + self::DICT_INVALID => 'does not match regular expression', + ]; + /** * @param string $pattern - * @param string $message */ - public function __construct($pattern, $message = 'does not match regular expression') + public function __construct($pattern) { $this->_pattern = $pattern; - $this->_message = $message; } public static function deserialize($configuration): SerializableValidator { - return new static($configuration->pattern, $configuration->message); + return new static($configuration->pattern); } public function serialize(): array { return [ 'pattern' => $this->_pattern, - 'message' => $this->_message, ]; } @@ -37,7 +39,7 @@ protected function _doValidate($value): Generator { if(preg_match($this->_pattern, $value) !== 1) { - yield $this->_makeError($this->_message); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } } diff --git a/src/Validators/RequiredValidator.php b/src/Validators/RequiredValidator.php index afe61a5..7675133 100644 --- a/src/Validators/RequiredValidator.php +++ b/src/Validators/RequiredValidator.php @@ -7,6 +7,12 @@ class RequiredValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; + + protected $_dictionary = [ + self::DICT_INVALID => 'required', + ]; + public static function deserialize($configuration): SerializableValidator { return new static(); @@ -21,7 +27,7 @@ protected function _doValidate($value): Generator { if($value === null || $value === '') { - yield $this->_makeError('required'); + yield $this->_makeError($this->getDictionary()[self::DICT_INVALID]); } } } diff --git a/src/Validators/StringValidator.php b/src/Validators/StringValidator.php index ec79917..45a123f 100644 --- a/src/Validators/StringValidator.php +++ b/src/Validators/StringValidator.php @@ -7,9 +7,19 @@ class StringValidator extends AbstractSerializableValidator { + public const DICT_INVALID = 'invalid'; + public const DICT_MIN = 'min'; + public const DICT_MAX = 'max'; + protected $_minLength; protected $_maxLength; + protected $_dictionary = [ + self::DICT_MIN => 'must be at least %s characters', + self::DICT_MAX => 'must be no more than %s characters', + self::DICT_INVALID => 'invalid string', + ]; + /** * @param int $minLength Min length in bytes, 0 to disable * @param int $maxLength Max length in bytes, 0 to disable @@ -44,11 +54,13 @@ protected function _doValidate($value): Generator $len = strlen($value); if($len < $this->_minLength) { - yield $this->_makeError('must be at least ' . $this->_minLength . ' characters'); + $err = str_replace('%s', $this->_minLength, $this->getDictionary()[self::DICT_MIN]); + yield $this->_makeError($err); } else if(($this->_maxLength > 0) && ($len > $this->_maxLength)) { - yield $this->_makeError('must be no more than ' . $this->_maxLength . ' characters'); + $err = str_replace('%s', $this->_maxLength, $this->getDictionary()[self::DICT_MAX]); + yield $this->_makeError($err); } } @@ -67,5 +79,4 @@ public function getMaxLength(): int { return $this->_maxLength; } - } diff --git a/tests/RegexValidatorTest.php b/tests/RegexValidatorTest.php index 87cb065..ad11f87 100644 --- a/tests/RegexValidatorTest.php +++ b/tests/RegexValidatorTest.php @@ -10,7 +10,7 @@ class RegexValidatorTest extends TestCase public function testRegexValidatorMessage() { $v1 = new RegexValidator('/^[0-9]{6}$/'); - $v2 = new RegexValidator('/^[0-9]{6}$/', 'test failure message'); + $v2 = RegexValidator::withDictionary([RegexValidator::DICT_INVALID => 'test failure message'], '/^[0-9]{6}$/'); $v1err = $v1->validate('123'); $this->assertNotEmpty($v1err); $this->assertEquals('does not match regular expression', $v1err[0]->getMessage()); diff --git a/tests/RequiredValidatorTest.php b/tests/RequiredValidatorTest.php index dae85e1..9ad4bba 100644 --- a/tests/RequiredValidatorTest.php +++ b/tests/RequiredValidatorTest.php @@ -1,6 +1,8 @@ assertTrue($validator->isValid(0)); $this->assertTrue($validator->isValid(false)); $this->assertTrue($validator->isValid(true)); + $this->assertEquals($validator->validate('')[0]->getMessage(), 'required'); + + $validator = RequiredValidator::withDictionary([ + RequiredValidator::DICT_INVALID => 'This field is required', + ]); + $this->assertFalse($validator->isValid(null)); + $this->assertFalse($validator->isValid('')); + $this->assertEquals($validator->validate('')[0]->getMessage(), 'This field is required'); + // test the error message + } + + public function testSerialize() + { + $validator = new RequiredValidator(); + $this->assertEquals(true, $validator->isValid('Something')); + $jsn = json_encode($validator); + + $unsValidator = Validation::fromJsonObject(json_decode($jsn)); + $this->assertInstanceOf(get_class($validator), $unsValidator); + $this->assertEquals(true, $validator->isValid('Something')); + $this->assertEquals(json_encode($validator), json_encode($unsValidator)); + } + + public function testSerializeWithDictionary() + { + $validator = RequiredValidator::withDictionary([ + RequiredValidator::DICT_INVALID => 'This field is required', + ]); + $this->assertEquals(false, $validator->isValid(null)); + $this->assertEquals(false, $validator->isValid('')); + $this->assertEquals('This field is required', $validator->validate('')[0]->getMessage()); + + $jsn = json_encode($validator); + $unsValidator = Validation::fromJsonObject(json_decode($jsn)); + $this->assertInstanceOf(get_class($validator), $unsValidator); + $this->assertEquals(false, $unsValidator->isValid(null)); + $this->assertEquals(false, $unsValidator->isValid('')); + $this->assertEquals('This field is required', $unsValidator->validate('')[0]->getMessage()); } }