diff --git a/packages/algoliasearch-helper/src/SearchParameters/index.js b/packages/algoliasearch-helper/src/SearchParameters/index.js index 4ff71f34431..21d015bf6ea 100644 --- a/packages/algoliasearch-helper/src/SearchParameters/index.js +++ b/packages/algoliasearch-helper/src/SearchParameters/index.js @@ -225,6 +225,13 @@ function SearchParameters(newParameters) { */ SearchParameters.PARAMETERS = Object.keys(new SearchParameters()); +// Returns a finite number or null. Used to reject Infinity/-Infinity from parseFloat. +function parseFiniteFloat(value) { + var n = parseFloat(value); + // global isFinite is safe here: n is always a number type (no string coercion risk) + return isFinite(n) ? n : null; +} + /** * @private * @param {object} partialState full or part of a state @@ -254,8 +261,18 @@ SearchParameters._parseNumbers = function (partialState) { var value = partialState[k]; if (typeof value === 'string') { var parsedValue = parseFloat(value); - // global isNaN is ok to use here, value is only number or NaN - numbers[k] = isNaN(parsedValue) ? value : parsedValue; + if (isNaN(parsedValue)) { + // Unparseable string like "all" — keep original + numbers[k] = value; + } else if (!isFinite(parsedValue)) { + // Infinity/-Infinity — convert to null + numbers[k] = null; + } else { + numbers[k] = parsedValue; + } + } else if (typeof value === 'number' && !isFinite(value)) { + // Already-numeric Infinity — convert to null + numbers[k] = null; } }); @@ -267,7 +284,13 @@ SearchParameters._parseNumbers = function (partialState) { ) { if (Array.isArray(geoRect)) { return geoRect.map(function (value) { - return parseFloat(value); + if (typeof value === 'string') { + return parseFiniteFloat(value); + } + if (typeof value === 'number' && !isFinite(value)) { + return null; + } + return value; }); } return geoRect; @@ -285,12 +308,17 @@ SearchParameters._parseNumbers = function (partialState) { if (Array.isArray(v)) { return v.map(function (vPrime) { if (typeof vPrime === 'string') { - return parseFloat(vPrime); + return parseFiniteFloat(vPrime); + } + if (typeof vPrime === 'number' && !isFinite(vPrime)) { + return null; } return vPrime; }); } else if (typeof v === 'string') { - return parseFloat(v); + return parseFiniteFloat(v); + } else if (typeof v === 'number' && !isFinite(v)) { + return null; } return v; }); diff --git a/packages/algoliasearch-helper/test/spec/SearchParameters/_parseNumbers.js b/packages/algoliasearch-helper/test/spec/SearchParameters/_parseNumbers.js index 1f1227a61bf..187d349cef1 100644 --- a/packages/algoliasearch-helper/test/spec/SearchParameters/_parseNumbers.js +++ b/packages/algoliasearch-helper/test/spec/SearchParameters/_parseNumbers.js @@ -102,3 +102,95 @@ test('_parseNumbers should convert nested numericRefinements values', function ( expect(actual.numericRefinements.foo['>=']).toEqual([[4.8], 15.16]); expect(actual.numericRefinements.foo['=']).toEqual([23.42]); }); + +test('_parseNumbers should convert string "Infinity" to null for number keys', function () { + var partialState = { + page: 'Infinity', + hitsPerPage: '-Infinity', + }; + var actual = SearchParameters._parseNumbers(partialState); + + expect(actual.page).toBe(null); + expect(actual.hitsPerPage).toBe(null); +}); + +test('_parseNumbers should convert numeric Infinity to null for number keys', function () { + var partialState = { + page: Infinity, + hitsPerPage: -Infinity, + }; + var actual = SearchParameters._parseNumbers(partialState); + + expect(actual.page).toBe(null); + expect(actual.hitsPerPage).toBe(null); +}); + +test('_parseNumbers should convert "Infinity" to null while keeping "all" as string', function () { + var partialState = { + aroundRadius: 'all', + page: 'Infinity', + }; + var actual = SearchParameters._parseNumbers(partialState); + + expect(actual.aroundRadius).toBe('all'); + expect(actual.page).toBe(null); +}); + +test('_parseNumbers should convert string "Infinity" to null in insideBoundingBox', function () { + var partialState = { + insideBoundingBox: [['Infinity', '9.66', '-Infinity', '-5.55']], + }; + var actual = SearchParameters._parseNumbers(partialState); + + expect(actual.insideBoundingBox).toEqual([[null, 9.66, null, -5.55]]); +}); + +test('_parseNumbers should convert numeric Infinity to null in insideBoundingBox', function () { + var partialState = { + insideBoundingBox: [[Infinity, 9.66, -Infinity, -5.55]], + }; + var actual = SearchParameters._parseNumbers(partialState); + + expect(actual.insideBoundingBox).toEqual([[null, 9.66, null, -5.55]]); +}); + +test('_parseNumbers should convert string "Infinity" to null in numericRefinements', function () { + var partialState = { + numericRefinements: { + price: { + '>=': ['Infinity', '-Infinity'], + '<=': [['Infinity']], + }, + }, + }; + var actual = SearchParameters._parseNumbers(partialState); + + expect(actual.numericRefinements.price['>=']).toEqual([null, null]); + expect(actual.numericRefinements.price['<=']).toEqual([[null]]); +}); + +test('_parseNumbers should convert numeric Infinity to null in numericRefinements', function () { + var partialState = { + numericRefinements: { + price: { + '>=': [Infinity, -Infinity], + '<=': [[Infinity]], + }, + }, + }; + var actual = SearchParameters._parseNumbers(partialState); + + expect(actual.numericRefinements.price['>=']).toEqual([null, null]); + expect(actual.numericRefinements.price['<=']).toEqual([[null]]); +}); + +test('_parseNumbers should convert numeric NaN to null for number keys', function () { + var partialState = { + page: NaN, + hitsPerPage: NaN, + }; + var actual = SearchParameters._parseNumbers(partialState); + + expect(actual.page).toBe(null); + expect(actual.hitsPerPage).toBe(null); +});