diff --git a/README.md b/README.md index 0306642..9e8f26f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ 1. [Types](#types) 1. [Example of custom validator](#example-of-custom-validator) 1. [Example of additional validator](#example-of-additional-validator) -1. [Schema definition Example](#schema-definition-example) +1. [Example of Schema definition](#example-of-schema-definition) 1. [Example of schema in schema](#example-of-schema-in-schema) 1. [Schema keys description](#schema-keys-description) 1. [Custom validation messages](#custom-validation-messages) @@ -112,12 +112,13 @@ results.then((errors) => { | setError | key: String, message: String, index: Number | Set error on field | | setModelError | path: String, message: String | Set error on model tree node | | getDefaultValues | | Get default values for model using defined schema | -| getField | name: String | Get field schema | -| getField | | Get all fields schemas | +| getField | name: String | Get field properties extended by parent schema instance (`parentSchema`) | +| getFields | | Get all fields schemas | | oneOfTypes | types: Array of types | Give posibility to validate one of type (Static method) | | pick | fieldsToPick: [String] | Get fields from schema by keys | | omit | fieldsToOmit: [String] | Get fields from schema and omit fieldsToOmit | | extend | fieldsToExtend: [String] | Extend schema by new fields or overwrite them | +| extendFieldValidators | fieldName: String, validator: { validator: Function, errorMessage: String or Function } | Extend field validators | | registerType | type: SchemaType | Register new schema type | | isValidatorRegistred | validatorName: String | Check model validator exists in schema | | addValidator | validatorName: String, validator: Function(model: Object, schema: instance of Schema) | Add model validator | @@ -139,12 +140,21 @@ results.then((errors) => { | SchemaType | You can register new schema type that has name, validator, validator when field is required (requiredValidator) and getDefaultValue | | [OneOfTypesAbove] | This type check value is array of type | +### Custom validator attributes + +| Name | Description | +|---|---| +| value | Field value | +| field | Field properties | +| model | Validated object | +| schema | Field parent schema instance | + #### Example of custom validator This validator will check two fields. You can validate one field on base another field. ```js const validateIfFieldTitleIsFilled = (minLength, message) => ({ - validator: (value, fieldSchema, formData) => { - if(formData.title){ + validator: (value, field, model, schema) => { + if (model.title) { return !!value; } return true; @@ -202,7 +212,7 @@ const validateIfOfAge = () => ({ }); ``` -### Schema definition Example +### Example of Schema definition If You want create new schema You must put object to constructor with information about object keys names and type of value on key. diff --git a/src/Schema.js b/src/Schema.js index 6dc4d3f..f4f14c7 100644 --- a/src/Schema.js +++ b/src/Schema.js @@ -123,7 +123,7 @@ class Schema { } getField(name) { - return this.schema[name]; + return { ...this.schema[name], parentSchema: this }; } getFields() { @@ -202,6 +202,7 @@ class Schema { schemaKeys.forEach((key) => { const value = validatedObject[key]; const fieldSchema = this.schema[key]; + const validators = this.getFieldValidators(key); const isArrayOfType = Array.isArray(fieldSchema.type); const fieldType = isArrayOfType ? fieldSchema.type[0] : fieldSchema.type; if (isArrayOfType && this.validateType(Array, value)) { @@ -213,7 +214,7 @@ class Schema { } this.validateRequired(fieldSchema, value, key); this.validateCustomValidators({ - validators: fieldSchema.validators, + validators, value, fieldSchema, validatedObject, @@ -240,11 +241,8 @@ class Schema { } validateCustomValidators({ validators, value, fieldSchema, validatedObject, key }) { - if (!validators) { - return; - } validators.forEach(({ validator, errorMessage }) => { - const results = validator(value, fieldSchema, validatedObject); + const results = validator(value, fieldSchema, validatedObject, this); if (results instanceof Promise) { const promise = results.then((result) => { this.resolveValidatorErrorsForKey(key, errorMessage, result); @@ -435,6 +433,35 @@ class Schema { }); } + getFieldValidators(fieldName) { + return this.schema[fieldName].validators || []; + } + + setFieldValidator(fieldName, validator) { + if (!Array.isArray(this.schema[fieldName].validators)) { + this.schema[fieldName].validators = []; + } + this.schema[fieldName].validators.push(validator); + } + + extendFieldValidators(fieldName, validator) { + const validators = this.getFieldValidators(fieldName); + if (!validators.length) { + this.setFieldValidator(fieldName, validator); + return; + } + if ( + validators.indexOf(validator) > -1 || + ( + validator.id && + validators.findIndex(validatorItem => validatorItem.id === validator.id) > -1 + ) + ) { + return; + } + this.setFieldValidator(fieldName, validator); + } + registerTypeIfNotExists(type, typeName) { if (type instanceof SchemaType && typeof this.typesValidators[typeName] !== 'function') { this.registerType(type); diff --git a/src/Schema.test.js b/src/Schema.test.js index ed597a6..63fa533 100644 --- a/src/Schema.test.js +++ b/src/Schema.test.js @@ -1013,6 +1013,88 @@ describe('Schema', () => { property: [customErrorMessage], }); }); + + describe('Should extend validators on field', () => { + let fooValidator; + let customValidator; + let schema; + let schemaWithValidators; + beforeEach(() => { + const customErrorMessage = 'foo error'; + fooValidator = { + validator: () => false, + errorMessage: () => customErrorMessage, + }; + customValidator = { + validator: () => false, + errorMessage: () => customErrorMessage, + }; + schema = new Schema({ + property: { + type: String, + }, + }); + schemaWithValidators = new Schema({ + property: { + type: String, + validators: [customValidator], + }, + }); + }); + + it('when validators property not exist on field', () => { + schema.extendFieldValidators('property', fooValidator); + expect(schema.getFieldValidators('property')).toEqual([fooValidator]); + }); + + it('when validators property exist on field', () => { + schemaWithValidators.extendFieldValidators('property', fooValidator); + expect(schemaWithValidators.getFieldValidators('property')).toEqual([customValidator, fooValidator]); + }); + + it('when validators property exist on field and validator has id', () => { + customValidator.id = 'customValidator'; + fooValidator.id = 'fooValidator'; + schemaWithValidators.extendFieldValidators('property', fooValidator); + expect(schemaWithValidators.getFieldValidators('property')).toEqual([customValidator, fooValidator]); + }); + }); + describe('Should not extend validators on field', () => { + let fooValidator; + let customValidator; + let schemaWithValidators; + beforeEach(() => { + const customErrorMessage = 'foo error'; + fooValidator = { + validator: () => false, + errorMessage: () => customErrorMessage, + }; + customValidator = { + validator: () => false, + errorMessage: () => customErrorMessage, + }; + schemaWithValidators = new Schema({ + property: { + type: String, + validators: [customValidator], + }, + }); + }); + + it('when validator exist in validators property', () => { + schemaWithValidators.setFieldValidator = jest.fn(); + schemaWithValidators.extendFieldValidators('property', customValidator); + expect(schemaWithValidators.setFieldValidator).not.toBeCalled(); + }); + + it('when validator with the same id exist in validators property', () => { + customValidator.id = 'customValidator'; + fooValidator.id = 'customValidator'; + schemaWithValidators.setFieldValidator = jest.fn(); + schemaWithValidators.extendFieldValidators('property', fooValidator); + expect(schemaWithValidators.setFieldValidator).not.toBeCalled(); + }); + }); }); describe('Default value', () => {