From 99d4c6aa8d3a666985214e3f1d09e2ccec6d6204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 9 Apr 2015 16:57:38 +0200 Subject: [PATCH] Add new strict mode "validate" When a model is configured with `strict: 'validate'`, any dynamic properties not included in the schema trigger a validation error. --- lib/model.js | 18 ++++++++++++++++-- lib/validations.js | 16 +++++++++++++++- test/loopback-dl.test.js | 11 +++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/lib/model.js b/lib/model.js index d8eedeb5..b0814158 100644 --- a/lib/model.js +++ b/lib/model.js @@ -120,14 +120,26 @@ ModelBaseClass.prototype._initProperties = function (data, options) { enumerable: false, configurable: true, value: false - } + }, }); + + if (strict === 'validate') { + Object.defineProperty(this, '__unknownProperties', { + writable: true, + enumerable: false, + configrable: true, + value: [] + }); + } } else { this.__cachedRelations = {}; this.__data = {}; this.__dataSource = options.dataSource; this.__strict = strict; this.__persisted = false; + if (strict === 'validate') { + this.__unknownProperties = []; + } } if (options.persisted !== undefined) { @@ -207,6 +219,8 @@ ModelBaseClass.prototype._initProperties = function (data, options) { } } else if (strict === 'throw') { throw new Error('Unknown property: ' + p); + } else if (strict === 'validate') { + this.__unknownProperties.push(p); } } } @@ -616,7 +630,7 @@ ModelBaseClass.observe = function(operation, listener) { */ ModelBaseClass.removeObserver = function(operation, listener) { if (!this._observers[operation]) return; - + var index = this._observers[operation].indexOf(listener); if (index != -1) this._observers[operation].splice(index, 1); }; diff --git a/lib/validations.js b/lib/validations.js index 086349c1..5af7cc38 100644 --- a/lib/validations.js +++ b/lib/validations.js @@ -411,8 +411,11 @@ Validatable.prototype.isValid = function (callback, data) { var valid = true, inst = this, wait = 0, async = false; var validations = this.constructor.validations; + var reportDiscardedProperties = this.__strict === 'validate' && + this.__unknownProperties && this.__unknownProperties.length; + // exit with success when no errors - if (typeof validations !== 'object') { + if (typeof validations !== 'object' && !reportDiscardedProperties) { cleanErrors(this); if (callback) { this.trigger('validate', function (validationsDone) { @@ -453,6 +456,16 @@ Validatable.prototype.isValid = function (callback, data) { }); }); + if (reportDiscardedProperties) { + for (var ix in inst.__unknownProperties) { + var key = inst.__unknownProperties[ix]; + var code = 'unknown-property'; + var msg = defaultMessages[code]; + inst.errors.add(key, msg, code); + valid = false; + } + } + if (!async) { validationsDone.call(inst, function () { if (valid) cleanErrors(inst); @@ -568,6 +581,7 @@ function skipValidation(inst, conf, kind) { var defaultMessages = { presence: 'can\'t be blank', absence: 'can\'t be set', + 'unknown-property': 'is not defined in the model', length: { min: 'too short', max: 'too long', diff --git a/test/loopback-dl.test.js b/test/loopback-dl.test.js index 60cd1eda..f360e4b1 100644 --- a/test/loopback-dl.test.js +++ b/test/loopback-dl.test.js @@ -456,6 +456,17 @@ describe('DataSource define model', function () { done(null, User); }); + describe('strict mode "validate"', function() { + it('should report validation error for unknown properties', function() { + var ds = new DataSource('memory'); + var User = ds.define('User', { name: String }, { strict: 'validate' }); + var user = new User({ name: 'Joe', age: 20 }); + user.isValid().should.be.false; + var codes = user.errors && user.errors.codes || {}; + codes.should.have.property('age').eql(['unknown-property']); + }); + }); + it('should be able to define open models', function (done) { var ds = new DataSource('memory');