diff --git a/packages/core/database/lib/query/helpers/where.js b/packages/core/database/lib/query/helpers/where.js index a899bb55a6..f164b8560e 100644 --- a/packages/core/database/lib/query/helpers/where.js +++ b/packages/core/database/lib/query/helpers/where.js @@ -60,13 +60,17 @@ const castValue = (value, attribute) => { return value; }; -const processAttributeWhere = (attribute, where, ctx) => { +const processAttributeWhere = (attribute, where, operator = '$eq') => { if (_.isArray(where)) { - return where.map(sub => processAttributeWhere(attribute, sub, ctx)); + return where.map(sub => processAttributeWhere(attribute, sub, operator)); } if (!_.isPlainObject(where)) { - return castValue(where, attribute); + if (CAST_OPERATORS.includes(operator)) { + return castValue(where, attribute); + } + + return where; } const filters = {}; @@ -74,21 +78,11 @@ const processAttributeWhere = (attribute, where, ctx) => { for (const key in where) { const value = where[key]; - if (isOperator(key)) { - if (!_.isPlainObject(value)) { - if (CAST_OPERATORS.includes(key)) { - filters[key] = castValue(value, attribute); - } else { - filters[key] = value; - } - continue; - } - - filters[key] = processAttributeWhere(attribute, value, ctx); - continue; + if (!isOperator(key)) { + throw new Error(`Undefined attribute level operator ${key}`); } - throw new Error(`Undefined attribute level operator ${key}`); + filters[key] = processAttributeWhere(attribute, value, key); } return filters; @@ -145,7 +139,7 @@ const processWhere = (where, ctx) => { const attribute = meta.attributes[key]; if (!attribute) { - filters[qb.aliasColumn(key, alias)] = processAttributeWhere(null, value, ctx); + filters[qb.aliasColumn(key, alias)] = processAttributeWhere(null, value); continue; } @@ -179,7 +173,7 @@ const processWhere = (where, ctx) => { const columnName = toColumnName(meta, key); const aliasedColumnName = qb.aliasColumn(columnName, alias); - filters[aliasedColumnName] = processAttributeWhere(attribute, value, ctx); + filters[aliasedColumnName] = processAttributeWhere(attribute, value); continue; } diff --git a/packages/core/strapi/tests/filtering.test.e2e.js b/packages/core/strapi/tests/filtering.test.e2e.js index b8384457b2..7b06d8ee92 100644 --- a/packages/core/strapi/tests/filtering.test.e2e.js +++ b/packages/core/strapi/tests/filtering.test.e2e.js @@ -35,6 +35,9 @@ const product = { big_rank: { type: 'biginteger', }, + isChecked: { + type: 'boolean', + }, }, displayName: 'Product', singularName: 'product', @@ -51,6 +54,7 @@ const productFixtures = [ decimal_field: 42.43, rank: 42, big_rank: '345678912983', + isChecked: true, }, { name: 'Product 2', @@ -59,6 +63,7 @@ const productFixtures = [ decimal_field: 91.22, rank: 82, big_rank: '926371623421', + isChecked: false, }, { name: 'Product 3', @@ -67,6 +72,7 @@ const productFixtures = [ decimal_field: 12.22, rank: 91, big_rank: '926372323421', + isChecked: true, }, { name: 'Product 4', @@ -75,6 +81,7 @@ const productFixtures = [ decimal_field: 12.22, rank: 99, big_rank: '999999999999', + isChecked: false, }, { name: 'Продукт 5, Product 5', @@ -83,6 +90,7 @@ const productFixtures = [ decimal_field: 142.43, rank: 142, big_rank: 345678912983, + isChecked: true, }, ]; @@ -1376,4 +1384,100 @@ describe('Filtering API', () => { expect(res.body.data).toEqual(expect.arrayContaining([data.product[4]])); }); }); + + describe('Type casting', () => { + describe('Booleans', () => { + test.each(['1', 'true', true, 't'])('Cast truthy booleans %s', async val => { + const res = await rq({ + method: 'GET', + url: '/products', + qs: { + filters: { + isChecked: val, + }, + }, + }); + expect(res.body.data).toEqual( + expect.arrayContaining([data.product[0], data.product[2], data.product[4]]) + ); + }); + + test.each(['1', 'true', true, 't'])('Cast truthy booleans nested %s', async val => { + const res = await rq({ + method: 'GET', + url: '/products', + qs: { + filters: { + isChecked: { + $eq: val, + }, + }, + }, + }); + expect(res.body.data).toEqual( + expect.arrayContaining([data.product[0], data.product[2], data.product[4]]) + ); + }); + + test.each(['1', 'true', true, 't'])('Cast truthy booleans in arrays %s', async val => { + const res = await rq({ + method: 'GET', + url: '/products', + qs: { + filters: { + isChecked: { + $in: [val], + }, + }, + }, + }); + expect(res.body.data).toEqual( + expect.arrayContaining([data.product[0], data.product[2], data.product[4]]) + ); + }); + + test.each(['0', 'false', false, 'f'])('Cast truthy booleans %s', async val => { + const res = await rq({ + method: 'GET', + url: '/products', + qs: { + filters: { + isChecked: val, + }, + }, + }); + expect(res.body.data).toEqual(expect.arrayContaining([data.product[1], data.product[3]])); + }); + + test.each(['0', 'false', false, 'f'])('Cast truthy booleans nested %s', async val => { + const res = await rq({ + method: 'GET', + url: '/products', + qs: { + filters: { + isChecked: { + $eq: val, + }, + }, + }, + }); + expect(res.body.data).toEqual(expect.arrayContaining([data.product[1], data.product[3]])); + }); + + test.each(['0', 'false', false, 'f'])('Cast truthy booleans in arrays %s', async val => { + const res = await rq({ + method: 'GET', + url: '/products', + qs: { + filters: { + isChecked: { + $in: [val], + }, + }, + }, + }); + expect(res.body.data).toEqual(expect.arrayContaining([data.product[1], data.product[3]])); + }); + }); + }); });