diff --git a/docs/validation.md b/docs/validation.md index 722cfc5..a98be33 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -255,3 +255,49 @@ errors; /* ] */ ``` + +### Validate raw data + +In addition to the _instance_ `validate()` method, Structure also adds a _static_ `validate()` to your structure classes that receives a _raw object_ or a _structure instance_ as parameter and has the same return type of the [normal validation](#validation): + +```javascript +const User = attributes({ + name: { + type: String, + minLength: 10 + }, + age: { + type: Number, + required: true + } +})(class User { }); + +// Using a raw object +const rawData = { + name: 'John' +}; + +const { valid, errors } = User.validate(rawData); + +valid; // false +errors; /* +[ + { message: '"name" length must be at least 10 characters long', path: 'name' }, + { message: '"age" is required', path: 'age' } +] +*/ + +// Using a structure instance +const user = new User({ + name: 'Some long name' +}); + +const validation = User.validate(user); + +validation.valid; // false +validation.errors; /* +[ + { message: '"age" is required', path: 'age' } +] +*/ +``` diff --git a/src/attributesDecorator.js b/src/attributesDecorator.js index b1bc110..f292f86 100644 --- a/src/attributesDecorator.js +++ b/src/attributesDecorator.js @@ -3,10 +3,14 @@ const { getInitialValues } = require('./initialValueCreation'); const { SCHEMA } = require('./symbols'); const { attributesDescriptor, - validationDescriptor, serializationDescriptor } = require('./propertyDescriptors'); +const { + validationDescriptor, + staticValidationDescriptor +} = require('./validation'); + const define = Object.defineProperty; function attributesDecorator(declaredSchema) { @@ -40,6 +44,8 @@ function attributesDecorator(declaredSchema) { value: declaredSchema }); + define(WrapperClass, 'validate', staticValidationDescriptor); + define(WrapperClass.prototype, SCHEMA, { value: declaredSchema }); diff --git a/src/propertyDescriptors.js b/src/propertyDescriptors.js index aec2459..4cfa80e 100644 --- a/src/propertyDescriptors.js +++ b/src/propertyDescriptors.js @@ -1,4 +1,4 @@ -const { SCHEMA, ATTRIBUTES, VALIDATE } = require('./symbols'); +const { SCHEMA, ATTRIBUTES } = require('./symbols'); const { NON_OBJECT_ATTRIBUTES } = require('./errorMessages'); const { serialize } = require('./serialization'); @@ -30,24 +30,6 @@ exports.attributesDescriptor = { } }; -exports.validationDescriptor = { - value: function validate() { - const validation = this[SCHEMA][VALIDATE]; - const serializedStructure = this.toJSON(); - - const errors = validation.validate(serializedStructure); - - if(errors) { - return { - valid: false, - errors - }; - } - - return { valid: true }; - } -}; - exports.serializationDescriptor = { value: function toJSON() { return serialize(this); diff --git a/src/validation/index.js b/src/validation/index.js index 308a8ea..135c63c 100644 --- a/src/validation/index.js +++ b/src/validation/index.js @@ -1,5 +1,7 @@ const joi = require('joi'); +const { SCHEMA, VALIDATE } = require('../symbols'); + const validations = [ require('./string'), require('./number'), @@ -10,7 +12,7 @@ const validations = [ const nestedValidation = require('./nested'); const arrayValidation = require('./array'); -function validationForAttribute(typeDescriptor) { +exports.validationForAttribute = function validationForAttribute(typeDescriptor) { if(typeDescriptor.itemType !== undefined) { return arrayValidation(typeDescriptor, typeDescriptor.itemType); } @@ -22,7 +24,7 @@ function validationForAttribute(typeDescriptor) { } return validation.createJoiSchema(typeDescriptor); -} +}; const mapDetail = ({ message, path }) => ({ message, path }); @@ -32,7 +34,7 @@ const validatorOptions = { allowUnknown: false }; -function validationForSchema(schema) { +exports.validationForSchema = function validationForSchema(schema) { const schemaValidation = {}; Object.keys(schema).forEach((attributeName) => { @@ -54,7 +56,38 @@ function validationForSchema(schema) { return validationErrors; } }; -} +}; -exports.validationForAttribute = validationForAttribute; -exports.validationForSchema = validationForSchema; +exports.validationDescriptor = { + value: function validate() { + const validation = this[SCHEMA][VALIDATE]; + const serializedStructure = this.toJSON(); + + return validateData(validation, serializedStructure); + } +}; + +exports.staticValidationDescriptor = { + value: function validate(data) { + if(data[SCHEMA]) { + data = data.toJSON(); + } + + const validation = this[SCHEMA][VALIDATE]; + + return validateData(validation, data); + } +}; + +function validateData(validation, data) { + const errors = validation.validate(data); + + if(errors) { + return { + valid: false, + errors + }; + } + + return { valid: true }; +} diff --git a/test/unit/validation/staticMethod.spec.js b/test/unit/validation/staticMethod.spec.js new file mode 100644 index 0000000..3ef4504 --- /dev/null +++ b/test/unit/validation/staticMethod.spec.js @@ -0,0 +1,81 @@ +const { attributes } = require('../../../src'); +const { expect } = require('chai'); + +describe('validation', () => { + describe('Using structure static method', () => { + var User; + + before(() => { + User = attributes({ + name: { + type: String, + required: true + }, + age: { + type: Number, + min: 21 + } + })(class User { }); + }); + + context('when data is valid', () => { + it('returns valid as true and no errors', () => { + const { valid, errors } = User.validate({ + name: 'The name', + age: 25 + }); + + expect(valid).to.be.true; + expect(errors).to.be.undefined; + }); + }); + + context('when data is invalid', () => { + it('returns valid as false and array of errors', () => { + const { valid, errors } = User.validate({ + age: 10 + }); + + expect(valid).to.be.false; + expect(errors).to.be.instanceOf(Array); + expect(errors).to.have.lengthOf(2); + expect(errors[0].path).to.equal('name'); + expect(errors[1].path).to.equal('age'); + }); + }); + + context('when passed data is a structure', () => { + context('when structure is valid', () => { + it('returns valid as true and no errors', () => { + const user = new User({ + name: 'Something', + age: 18 + }); + + const { valid, errors } = User.validate(user); + + expect(valid).to.be.false; + expect(errors).to.be.instanceOf(Array); + expect(errors).to.have.lengthOf(1); + expect(errors[0].path).to.equal('age'); + }); + }); + + context('when structure is invalid', () => { + it('returns valid as false and array of errors', () => { + const user = new User({ + age: 10 + }); + + const { valid, errors } = User.validate(user); + + expect(valid).to.be.false; + expect(errors).to.be.instanceOf(Array); + expect(errors).to.have.lengthOf(2); + expect(errors[0].path).to.equal('name'); + expect(errors[1].path).to.equal('age'); + }); + }); + }); + }); +});