Skip to content

Commit 485ad3e

Browse files
authored
Merge pull request #1219 from strongloop/backport/array-coercion
Coerce array-like objects into arrays
2 parents 553c944 + c62ba21 commit 485ad3e

File tree

2 files changed

+103
-13
lines changed

2 files changed

+103
-13
lines changed

lib/dao.js

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,6 +1537,27 @@ function NumberType(val) {
15371537
return !isNaN(num) ? num : val;
15381538
}
15391539

1540+
function coerceArray(val) {
1541+
if (Array.isArray(val)) {
1542+
return val;
1543+
}
1544+
1545+
if (!utils.isPlainObject(val)) {
1546+
throw new Error(g.f('Value is not an {{array}} or {{object}} with sequential numeric indices'));
1547+
}
1548+
1549+
var arrayVal = new Array(Object.keys(val).length);
1550+
for (var i = 0; i < arrayVal.length; ++i) {
1551+
if (!val.hasOwnProperty(i)) {
1552+
throw new Error(g.f('Value is not an {{array}} or {{object}} with sequential numeric indices'));
1553+
}
1554+
1555+
arrayVal[i] = val[i];
1556+
}
1557+
1558+
return arrayVal;
1559+
}
1560+
15401561
/*
15411562
* Coerce values based the property types
15421563
* @param {Object} where The where clause
@@ -1561,16 +1582,18 @@ DataAccessObject._coerce = function(where) {
15611582
// Handle logical operators
15621583
if (p === 'and' || p === 'or' || p === 'nor') {
15631584
var clauses = where[p];
1564-
if (Array.isArray(clauses)) {
1565-
for (var k = 0; k < clauses.length; k++) {
1566-
self._coerce(clauses[k]);
1567-
}
1568-
} else {
1569-
err = new Error(g.f('The %s operator has invalid clauses %j', p, clauses));
1585+
try {
1586+
clauses = coerceArray(clauses);
1587+
} catch (e) {
1588+
err = new Error(g.f('The %s operator has invalid clauses %j: %s', p, clauses, e.message));
15701589
err.statusCode = 400;
15711590
throw err;
15721591
}
15731592

1593+
for (var k = 0; k < clauses.length; k++) {
1594+
self._coerce(clauses[k]);
1595+
}
1596+
15741597
continue;
15751598
}
15761599
var DataType = props[p] && props[p].type;
@@ -1625,15 +1648,21 @@ DataAccessObject._coerce = function(where) {
16251648
switch (operator) {
16261649
case 'inq':
16271650
case 'nin':
1628-
if (!Array.isArray(val)) {
1629-
err = new Error(g.f('The %s property has invalid clause %j', p, where[p]));
1651+
case 'between':
1652+
try {
1653+
val = coerceArray(val);
1654+
} catch (e) {
1655+
err = new Error(g.f('The %s property has invalid clause %j: %s', p, where[p], e));
16301656
err.statusCode = 400;
16311657
throw err;
16321658
}
1633-
break;
1634-
case 'between':
1635-
if (!Array.isArray(val) || val.length !== 2) {
1636-
err = new Error(g.f('The %s property has invalid clause %j', p, where[p]));
1659+
1660+
if (operator === 'between' && val.length !== 2) {
1661+
err = new Error(g.f(
1662+
'The %s property has invalid clause %j: Expected precisely 2 values, received %d',
1663+
p,
1664+
where[p],
1665+
val.length));
16371666
err.statusCode = 400;
16381667
throw err;
16391668
}
@@ -1643,7 +1672,10 @@ DataAccessObject._coerce = function(where) {
16431672
case 'ilike':
16441673
case 'nilike':
16451674
if (!(typeof val === 'string' || val instanceof RegExp)) {
1646-
err = new Error(g.f('The %s property has invalid clause %j', p, where[p]));
1675+
err = new Error(g.f(
1676+
'The %s property has invalid clause %j: Expected a string or RegExp',
1677+
p,
1678+
where[p]));
16471679
err.statusCode = 400;
16481680
throw err;
16491681
}
@@ -1660,6 +1692,14 @@ DataAccessObject._coerce = function(where) {
16601692
}
16611693
}
16621694
}
1695+
1696+
try {
1697+
// Coerce val into an array if it resembles an array-like object
1698+
val = coerceArray(val);
1699+
} catch (e) {
1700+
// NOOP when not coercable into an array.
1701+
}
1702+
16631703
// Coerce the array items
16641704
if (Array.isArray(val)) {
16651705
for (var i = 0; i < val.length; i++) {

test/loopback-dl.test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,6 +1476,56 @@ describe('DataAccessObject', function() {
14761476
assert.deepEqual(where, {and: [{age: 10}], vip: true});
14771477
});
14781478

1479+
var COERCIONS = [
1480+
{
1481+
in: {scores: {0: '10', 1: '20'}},
1482+
out: {scores: [10, 20]},
1483+
},
1484+
{
1485+
in: {and: {0: {age: '10'}, 1: {vip: 'true'}}},
1486+
out: {and: [{age: 10}, {vip: true}]},
1487+
},
1488+
{
1489+
in: {or: {0: {age: '10'}, 1: {vip: 'true'}}},
1490+
out: {or: [{age: 10}, {vip: true}]},
1491+
},
1492+
{
1493+
in: {id: {inq: {0: 'aaa', 1: 'bbb'}}},
1494+
out: {id: {inq: ['aaa', 'bbb']}},
1495+
},
1496+
{
1497+
in: {id: {nin: {0: 'aaa', 1: 'bbb'}}},
1498+
out: {id: {nin: ['aaa', 'bbb']}},
1499+
},
1500+
{
1501+
in: {scores: {between: {0: '0', 1: '42'}}},
1502+
out: {scores: {between: [0, 42]}},
1503+
},
1504+
];
1505+
1506+
COERCIONS.forEach(function(coercion) {
1507+
var inStr = JSON.stringify(coercion.in);
1508+
it('coerces where clause with array-like objects ' + inStr, function() {
1509+
assert.deepEqual(model._coerce(coercion.in), coercion.out);
1510+
});
1511+
});
1512+
1513+
var INVALID_CLAUSES = [
1514+
{scores: {inq: {0: '10', 1: '20', 4: '30'}}},
1515+
{scores: {inq: {0: '10', 1: '20', bogus: 'true'}}},
1516+
{scores: {between: {0: '10', 1: '20', 2: '30'}}},
1517+
];
1518+
1519+
INVALID_CLAUSES.forEach(function(where) {
1520+
var whereStr = JSON.stringify(where);
1521+
it('throws an error on malformed array-like object ' + whereStr,
1522+
function() {
1523+
assert.throws(
1524+
function() { model._coerce(where); },
1525+
/property has invalid clause/);
1526+
});
1527+
});
1528+
14791529
it('throws an error if the where property is not an object', function() {
14801530
try {
14811531
// The where clause has to be an object

0 commit comments

Comments
 (0)