From 0854009b22ee054af911fa2c31ff9da40e14288b Mon Sep 17 00:00:00 2001 From: Louis Chatriot Date: Wed, 15 May 2013 16:34:29 +0200 Subject: [PATCH] Use given _id if any, otherwise a uid --- lib/customUtils.js | 6 +++--- lib/datastore.js | 16 ++-------------- lib/model.js | 21 ++++++++++++++++++--- test/db.test.js | 20 +++++++++++++++++++- test/model.test.js | 12 ++++++++++++ 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/lib/customUtils.js b/lib/customUtils.js index 2e34170..c7e0819 100644 --- a/lib/customUtils.js +++ b/lib/customUtils.js @@ -19,12 +19,12 @@ function ensureDirectoryExists (dir, cb) { /** * Return a random alphanumerical string of length len - * There is a very small probability for the length to be less than len - * (il the base64 conversion yields to many pluses and slashes) but + * There is a very small probability (less than 1/1,000,000) for the length to be less than len + * (il the base64 conversion yields too many pluses and slashes) but * that's not an issue here */ function uid (len) { - return crypto.randomBytes(Math.ceil(len * 5 / 4)) + return crypto.randomBytes(Math.ceil(Math.max(8, len * 2))) .toString('base64') .replace(/[+\/]/g, '') .slice(0, len); diff --git a/lib/datastore.js b/lib/datastore.js index 64e2865..4adba0a 100644 --- a/lib/datastore.js +++ b/lib/datastore.js @@ -2,7 +2,6 @@ * The datastore itself * TODO * Queue operations - * Enable upserts * Update and removes should only modify the corresponding part of the database */ @@ -84,7 +83,7 @@ Datastore.prototype.insert = function (newDoc, cb) { ; try { - newDoc._id = customUtils.uid(16); + newDoc._id = newDoc._id || customUtils.uid(16); persistableNewDoc = model.serialize(newDoc); } catch (e) { return callback(e); @@ -185,17 +184,6 @@ Datastore.prototype.persistWholeDatabase = function (data, cb) { }; -/** - * Modify an object according to the updateQuery - * For now the updateQuery only replaces the object - */ -Datastore.modify = function (obj, updateQuery) { - updateQuery = model.deepCopy(updateQuery); - updateQuery._id = obj._id; - return updateQuery; -}; - - /** * Update all docs matching query * For now, very naive implementation (recalculating the whole database) @@ -238,7 +226,7 @@ Datastore.prototype.update = function (query, newDoc, options, cb) { self.data.forEach(function (d) { if (Datastore.match(d, query) && (multi || numReplaced === 0)) { numReplaced += 1; - newData.push(Datastore.modify(d, newDoc)); + newData.push(model.modify(d, newDoc)); } else { newData.push(d); } diff --git a/lib/model.js b/lib/model.js index 9620119..cc40126 100644 --- a/lib/model.js +++ b/lib/model.js @@ -11,17 +11,21 @@ var dateToJSON = function () { return { $$date: this.getTime() }; } * Serialize an object to be persisted to a one-line string * Accepted primitive types: Number, String, Boolean, Date, null * Accepted secondary types: Objects, Arrays - * TODO: throw an error if variable name begins with '$' */ function serialize (obj) { var res; - // Keep track of the fact this is a Date object + // Keep track of the fact that this is a Date object Date.prototype.toJSON = dateToJSON; res = JSON.stringify(obj, function (k, v) { + // Non-treatable edge case here: if part of the object if of the form { $$date: number } + // Its serialized-then-deserialized version it will transformed into a Date object + // But you really need to want it to trigger such behaviour, even when warned not to use '$' at the beginning of the field names... + if (k[0] === '$' && !(k === '$$date' && typeof v === 'number')) { throw 'Keys cannot begin with the $ character'; } + if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null) { return v; } - if (v && v.constructor && v.constructor.name === 'Date') { return { $$date: v.toString() }; } + //if (v && v.constructor && v.constructor.name === 'Date') { console.log("==============="); return { $$date: v.toString() }; } return v; }); @@ -80,8 +84,19 @@ function deepCopy (obj) { } +/** + * Modify a DB object according to an update query + * For now the updateQuery only replaces the object + */ +function modify (obj, updateQuery) { + updateQuery = deepCopy(updateQuery); + updateQuery._id = obj._id; + return updateQuery; +}; + // Interface module.exports.serialize = serialize; module.exports.deserialize = deserialize; module.exports.deepCopy = deepCopy; +module.exports.modify = modify; diff --git a/test/db.test.js b/test/db.test.js index 055d6a0..4d5a8df 100644 --- a/test/db.test.js +++ b/test/db.test.js @@ -36,7 +36,7 @@ describe('Database', function () { describe('Insert', function () { - it('Able to insert a document in the database and retrieve it even after a reload', function (done) { + it('Able to insert a document in the database, setting an _id if none provided, and retrieve it even after a reload', function (done) { d.find({}, function (err, docs) { docs.length.should.equal(0); @@ -132,6 +132,24 @@ describe('Database', function () { }); }); + it('Cannot insert a doc that has a field beginning with a $ sign', function (done) { + d.insert({ $something: 'atest' }, function (err) { + assert.isDefined(err); + done(); + }); + }); + + it('If an _id is already given when we insert a document, use it and not the default uid', function (done) { + d.insert({ _id: 'test', stuff: true }, function (err, newDoc) { + if (err) { return done(err); } + + newDoc.stuff.should.equal(true); + newDoc._id.should.equal('test'); + + done(); + }); + }); + }); // ==== End of 'Insert' ==== // diff --git a/test/model.test.js b/test/model.test.js index a52c1d7..f31e0ae 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -112,6 +112,18 @@ describe('Model', function () { done(); }); + it('Reject field names beginning with a $ sign', function (done) { + var a = { $something: 'totest' } + , b; + + try { + b = model.serialize(a); + return done('An error should have been thrown'); + } catch (e) { + return done(); + } + }); + }); // ==== End of 'Serialization, deserialization' ==== //