Merge pull request #157 from strongloop/feature/query-order

Normalize filter.order and enforce more checks
This commit is contained in:
Raymond Feng 2014-06-27 10:01:33 -07:00
commit d0e61af26f
3 changed files with 156 additions and 20 deletions

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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 = [
{