diff --git a/lib/model.js b/lib/model.js index 46893fb..9f12a0e 100644 --- a/lib/model.js +++ b/lib/model.js @@ -6,6 +6,8 @@ var dateToJSON = function () { return { $$date: this.getTime() }; } , originalDateToJSON = Date.prototype.toJSON + , util = require('util') + ; /** @@ -23,6 +25,26 @@ function checkKey (k, v) { } +/** + * Check a DB object and throw an error if it's not valid + * Works by applying the above checkKey function to all fields recursively + */ +function checkObject (obj) { + if (util.isArray(obj)) { + obj.forEach(function (o) { + checkObject(o); + }); + } + + if (typeof obj === 'object') { + Object.keys(obj).forEach(function (k) { + checkKey(k, obj[k]); + checkObject(obj[k]); + }); + } +} + + /** * Serialize an object to be persisted to a one-line string * Accepted primitive types: Number, String, Boolean, Date, null @@ -88,7 +110,6 @@ function deepCopy (obj) { if (typeof obj === 'object') { res = {}; Object.keys(obj).forEach(function (k) { - checkKey(k, obj[k]); res[k] = deepCopy(obj[k]); }); return res; @@ -105,6 +126,9 @@ function deepCopy (obj) { function modify (obj, updateQuery) { updateQuery = deepCopy(updateQuery); updateQuery._id = obj._id; + + checkObject(updateQuery); + return updateQuery; }; @@ -113,4 +137,5 @@ function modify (obj, updateQuery) { module.exports.serialize = serialize; module.exports.deserialize = deserialize; module.exports.deepCopy = deepCopy; +module.exports.checkObject = checkObject; module.exports.modify = modify; diff --git a/test/model.test.js b/test/model.test.js index 914c934..42490f8 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -127,6 +127,30 @@ describe('Model', function () { }); // ==== End of 'Serialization, deserialization' ==== // + describe('Object checking', function () { + + it('Field names beginning with a $ sign are forbidden', function () { + assert.isDefined(model.checkObject); + + (function () { + model.checkObject({ $bad: true }); + }).should.throw(); + + (function () { + model.checkObject({ some: 42, nested: { again: "no", $worse: true } }); + }).should.throw(); + + // This shouldn't throw since "$actuallyok" is not a field name + model.checkObject({ some: 42, nested: [ 5, "no", "$actuallyok", true ] }); + + (function () { + model.checkObject({ some: 42, nested: [ 5, "no", "$actuallyok", true, { $hidden: "useless" } ] }); + }).should.throw(); + }); + + }); // ==== End of 'Object checking' ==== // + + describe('Deep copying', function () { it('Should be able to deep copy any serializable model', function () { @@ -157,16 +181,6 @@ describe('Model', function () { res.subobj.b.should.equal('c'); }); - it('Will throw an error if obj contains a field beginning by the $ sign', function () { - (function () { - model.deepCopy({ $something: true }); - }).should.throw(); - - (function () { - model.deepCopy({ something: true, another: { $badfield: 'rrr' } }); - }).should.throw(); - }); - }); // ==== End of 'Deep copying' ==== // });