From e0c76199085eb09b94411e13171a20d033ccde48 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Thu, 26 Jun 2014 23:40:20 -0700 Subject: [PATCH] Normalize filter.order and enforce more checks --- lib/connectors/memory.js | 6 +++ lib/dao.js | 112 ++++++++++++++++++++++++++++++++------- test/memory.test.js | 58 ++++++++++++++++++++ 3 files changed, 156 insertions(+), 20 deletions(-) diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index b568a0f3..7441ddd6 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.js @@ -270,6 +270,12 @@ Memory.prototype.all = function all(model, filter, callback) { }.bind(this)); if (filter) { + if (!filter.order) { + var idNames = this.idNames(model); + if (idNames && idNames.length) { + filter.order = idNames; + } + } // do we need some sorting? if (filter.order) { var orders = filter.order; diff --git a/lib/dao.js b/lib/dao.js index 745b6996..131be88c 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -434,6 +434,48 @@ DataAccessObject._normalize = function (filter) { filter.skip = offset; } + if (filter.order) { + var order = filter.order; + if (!Array.isArray(order)) { + order = [order]; + } + var fields = []; + for (var i = 0, m = order.length; i < m; i++) { + if (typeof order[i] === 'string') { + // Normalize 'f1 ASC, f2 DESC, f3' to ['f1 ASC', 'f2 DESC', 'f3'] + var tokens = order[i].split(/(?:\s*,\s*)+/); + for (var t = 0, n = tokens.length; t < n; t++) { + var token = tokens[t]; + if (token.length === 0) { + // Skip empty token + continue; + } + var parts = token.split(/\s+/); + if (parts.length >= 2) { + var dir = parts[1].toUpperCase(); + if (dir === 'ASC' || dir === 'DESC') { + token = parts[0] + ' ' + dir; + } else { + err = new Error(util.format('The order %j has invalid direction', token)); + err.statusCode = 400; + throw err; + } + } + fields.push(token); + } + } else { + err = new Error(util.format('The order %j is not valid', order[i])); + err.statusCode = 400; + throw err; + } + } + if (fields.length === 1 && typeof filter.order === 'string') { + filter.order = fields[0]; + } else { + filter.order = fields; + } + } + // normalize fields as array of included property names if (filter.fields) { filter.fields = fieldsToArray(filter.fields, @@ -445,6 +487,25 @@ DataAccessObject._normalize = function (filter) { return filter; }; +function DateType(arg) { + return new Date(arg); +} + +function BooleanType(val) { + if (val === 'true') { + return true; + } else if (val === 'false') { + return false; + } else { + return Boolean(val); + } +} + +function NumberType(val) { + var num = Number(val); + return !isNaN(num) ? num : val; +} + /* * Coerce values based the property types * @param {Object} where The where clause @@ -470,11 +531,11 @@ DataAccessObject._coerce = function (where) { if (p === 'and' || p === 'or' || p === 'nor') { var clauses = where[p]; if (Array.isArray(clauses)) { - for (var i = 0; i < clauses.length; i++) { - self._coerce(clauses[i]); + for (var k = 0; k < clauses.length; k++) { + self._coerce(clauses[k]); } } else { - err = new Error(util.format('The %p operator has invalid clauses %j', p, clauses)); + err = new Error(util.format('The %s operator has invalid clauses %j', p, clauses)); err.statusCode = 400; throw err; } @@ -488,30 +549,16 @@ DataAccessObject._coerce = function (where) { DataType = DataType[0]; } if (DataType === Date) { - var OrigDate = Date; - DataType = function Date(arg) { - return new OrigDate(arg); - }; + DataType = DateType; } else if (DataType === Boolean) { - DataType = function (val) { - if (val === 'true') { - return true; - } else if (val === 'false') { - return false; - } else { - return Boolean(val); - } - }; + DataType = BooleanType; } else if (DataType === Number) { // This fixes a regression in mongodb connector // For numbers, only convert it produces a valid number // LoopBack by default injects a number id. We should fix it based // on the connector's input, for example, MongoDB should use string // while RDBs typically use number - DataType = function (val) { - var num = Number(val); - return !isNaN(num) ? num : val; - }; + DataType = NumberType; } if (!DataType) { @@ -543,6 +590,31 @@ DataAccessObject._coerce = function (where) { if (op in val) { val = val[op]; operator = op; + switch(operator) { + case 'inq': + case 'nin': + if (!Array.isArray(val)) { + err = new Error(util.format('The %s property has invalid clause %j', p, where[p])); + err.statusCode = 400; + throw err; + } + break; + case 'between': + if (!Array.isArray(val) || val.length !== 2) { + err = new Error(util.format('The %s property has invalid clause %j', p, where[p])); + err.statusCode = 400; + throw err; + } + break; + case 'like': + case 'nlike': + if (!(typeof val === 'string' || val instanceof RegExp)) { + err = new Error(util.format('The %s property has invalid clause %j', p, where[p])); + err.statusCode = 400; + throw err; + } + break; + } break; } } diff --git a/test/memory.test.js b/test/memory.test.js index 4016209e..40283b82 100644 --- a/test/memory.test.js +++ b/test/memory.test.js @@ -141,6 +141,64 @@ describe('Memory connector', function () { }); }); + it('should throw if the like value is not string or regexp', function (done) { + User.find({where: {name: {like: 123}}}, function (err, posts) { + should.exist(err); + done(); + }); + }); + + it('should throw if the nlike value is not string or regexp', function (done) { + User.find({where: {name: {nlike: 123}}}, function (err, posts) { + should.exist(err); + done(); + }); + }); + + it('should throw if the inq value is not an array', function (done) { + User.find({where: {name: {inq: '12'}}}, function (err, posts) { + should.exist(err); + done(); + }); + }); + + it('should throw if the nin value is not an array', function (done) { + User.find({where: {name: {nin: '12'}}}, function (err, posts) { + should.exist(err); + done(); + }); + }); + + it('should throw if the between value is not an array', function (done) { + User.find({where: {name: {between: '12'}}}, function (err, posts) { + should.exist(err); + done(); + }); + }); + + it('should throw if the between value is not an array of length 2', function (done) { + User.find({where: {name: {between: ['12']}}}, function (err, posts) { + should.exist(err); + done(); + }); + }); + + it('support order with multiple fields', function (done) { + User.find({order: 'vip ASC, seq DESC'}, function (err, posts) { + should.not.exist(err); + posts[0].seq.should.be.eql(4); + posts[1].seq.should.be.eql(3); + done(); + }); + }); + + it('should throw if order has wrong direction', function (done) { + User.find({order: 'seq ABC'}, function (err, posts) { + should.exist(err); + done(); + }); + }); + function seed(done) { var beatles = [ {