Normalize filter.order and enforce more checks
This commit is contained in:
parent
a1d3e72046
commit
e0c7619908
|
@ -270,6 +270,12 @@ Memory.prototype.all = function all(model, filter, callback) {
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
if (filter) {
|
if (filter) {
|
||||||
|
if (!filter.order) {
|
||||||
|
var idNames = this.idNames(model);
|
||||||
|
if (idNames && idNames.length) {
|
||||||
|
filter.order = idNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
// do we need some sorting?
|
// do we need some sorting?
|
||||||
if (filter.order) {
|
if (filter.order) {
|
||||||
var orders = filter.order;
|
var orders = filter.order;
|
||||||
|
|
112
lib/dao.js
112
lib/dao.js
|
@ -434,6 +434,48 @@ DataAccessObject._normalize = function (filter) {
|
||||||
filter.skip = offset;
|
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
|
// normalize fields as array of included property names
|
||||||
if (filter.fields) {
|
if (filter.fields) {
|
||||||
filter.fields = fieldsToArray(filter.fields,
|
filter.fields = fieldsToArray(filter.fields,
|
||||||
|
@ -445,6 +487,25 @@ DataAccessObject._normalize = function (filter) {
|
||||||
return 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
|
* Coerce values based the property types
|
||||||
* @param {Object} where The where clause
|
* @param {Object} where The where clause
|
||||||
|
@ -470,11 +531,11 @@ DataAccessObject._coerce = function (where) {
|
||||||
if (p === 'and' || p === 'or' || p === 'nor') {
|
if (p === 'and' || p === 'or' || p === 'nor') {
|
||||||
var clauses = where[p];
|
var clauses = where[p];
|
||||||
if (Array.isArray(clauses)) {
|
if (Array.isArray(clauses)) {
|
||||||
for (var i = 0; i < clauses.length; i++) {
|
for (var k = 0; k < clauses.length; k++) {
|
||||||
self._coerce(clauses[i]);
|
self._coerce(clauses[k]);
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
err.statusCode = 400;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -488,30 +549,16 @@ DataAccessObject._coerce = function (where) {
|
||||||
DataType = DataType[0];
|
DataType = DataType[0];
|
||||||
}
|
}
|
||||||
if (DataType === Date) {
|
if (DataType === Date) {
|
||||||
var OrigDate = Date;
|
DataType = DateType;
|
||||||
DataType = function Date(arg) {
|
|
||||||
return new OrigDate(arg);
|
|
||||||
};
|
|
||||||
} else if (DataType === Boolean) {
|
} else if (DataType === Boolean) {
|
||||||
DataType = function (val) {
|
DataType = BooleanType;
|
||||||
if (val === 'true') {
|
|
||||||
return true;
|
|
||||||
} else if (val === 'false') {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return Boolean(val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if (DataType === Number) {
|
} else if (DataType === Number) {
|
||||||
// This fixes a regression in mongodb connector
|
// This fixes a regression in mongodb connector
|
||||||
// For numbers, only convert it produces a valid number
|
// For numbers, only convert it produces a valid number
|
||||||
// LoopBack by default injects a number id. We should fix it based
|
// LoopBack by default injects a number id. We should fix it based
|
||||||
// on the connector's input, for example, MongoDB should use string
|
// on the connector's input, for example, MongoDB should use string
|
||||||
// while RDBs typically use number
|
// while RDBs typically use number
|
||||||
DataType = function (val) {
|
DataType = NumberType;
|
||||||
var num = Number(val);
|
|
||||||
return !isNaN(num) ? num : val;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DataType) {
|
if (!DataType) {
|
||||||
|
@ -543,6 +590,31 @@ DataAccessObject._coerce = function (where) {
|
||||||
if (op in val) {
|
if (op in val) {
|
||||||
val = val[op];
|
val = val[op];
|
||||||
operator = 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
function seed(done) {
|
||||||
var beatles = [
|
var beatles = [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue