Skip to content

Commit a001590

Browse files
ready for review
1 parent 76801e8 commit a001590

File tree

5 files changed

+82
-31
lines changed

5 files changed

+82
-31
lines changed

src/int_32.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BSON_INT32_MAX, BSON_INT32_MIN } from './constants';
33
import { BSONError } from './error';
44
import type { EJSONOptions } from './extended_json';
55
import { type InspectFn, defaultInspect } from './parser/utils';
6+
import { removeLeadingZeros } from './utils/string_utils';
67

78
/** @public */
89
export interface Int32Extended {
@@ -48,11 +49,7 @@ export class Int32 extends BSONValue {
4849
* @param value - the string we want to represent as an int32.
4950
*/
5051
static fromString(value: string): Int32 {
51-
const cleanedValue = !/[^0]+/.test(value)
52-
? value.replace(/^0+/, '0') // all zeros case
53-
: value[0] === '-'
54-
? value.replace(/^-0+/, '-') // negative number with leading zeros
55-
: value.replace(/^\+?0+/, ''); // positive number with leading zeros
52+
const cleanedValue = removeLeadingZeros(value);
5653

5754
const coercedValue = Number(value);
5855

src/long.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { BSONError } from './error';
33
import type { EJSONOptions } from './extended_json';
44
import { type InspectFn, defaultInspect } from './parser/utils';
55
import type { Timestamp } from './timestamp';
6+
import { removeLeadingZeros } from './utils/string_utils';
67

78
interface LongWASMHelpers {
89
/** Gets the high bits of the last operation performed */
@@ -261,16 +262,16 @@ export class Long extends BSONValue {
261262
const validCharRangeEnd = String.fromCharCode('a'.charCodeAt(0) + (radix - 11));
262263
regexInputString = `[^-0-9+(a-${validCharRangeEnd})]`;
263264
}
264-
const regex = new RegExp(regexInputString, '\i');
265+
const regex = new RegExp(regexInputString, 'i');
265266
return regex.test(str) ? false : str;
266267
}
267268

268269
/**
269270
* @internal
270271
* Returns a Long representation of the given string, written using the specified radix.
271-
* This method throws an error, if the following both of the conditions are true:
272+
* Throws an error if `throwsError` is set to true and any of the following conditions are true:
272273
* - the string contains invalid characters for the given radix
273-
* - throwsError is true
274+
* - the string contains whitespace
274275
* @param str - The textual representation of the Long
275276
* @param unsigned - Whether unsigned or not, defaults to signed
276277
* @param radix - The radix in which the text is written (2-36), defaults to 10
@@ -293,15 +294,20 @@ export class Long extends BSONValue {
293294
unsigned = !!unsigned;
294295
}
295296
radix = radix || 10;
297+
298+
if (radix < 2 || 36 < radix) throw new BSONError('radix');
299+
296300
if (throwsError && !Long.validateStringCharacters(str, radix)) {
297-
throw new BSONError(`Input: ${str} contains invalid characters for radix: ${radix}`);
301+
throw new BSONError(`Input: '${str}' contains invalid characters for radix: ${radix}`);
302+
}
303+
if (throwsError && str.trim() !== str) {
304+
throw new BSONError(`Input: '${str}' contains whitespace.`);
298305
}
299-
if (radix < 2 || 36 < radix) throw new BSONError('radix');
300306

301307
let p;
302308
if ((p = str.indexOf('-')) > 0) throw new BSONError('interior hyphen');
303309
else if (p === 0) {
304-
return Long.fromStringHelper(str.substring(1), unsigned, radix).neg();
310+
return Long.fromStringHelper(str.substring(1), unsigned, radix, throwsError).neg();
305311
}
306312

307313
// Do several (8) digits each time through the loop, so as to
@@ -325,16 +331,25 @@ export class Long extends BSONValue {
325331
}
326332

327333
/**
328-
* @internal - TODO(NODE-XXXX): fromStrictString throws on overflow
329334
* Returns a Long representation of the given string, written using the specified radix.
330-
* If the string contains invalid characters for the given radix, this function will throw an error.
335+
* Throws an error if any of the following conditions are true:
336+
* - the string contains invalid characters for the given radix
337+
* - the string contains whitespace
338+
* - the value the string represents is too large or too small to be a Long
331339
* @param str - The textual representation of the Long
332340
* @param unsigned - Whether unsigned or not, defaults to signed
333341
* @param radix - The radix in which the text is written (2-36), defaults to 10
334342
* @returns The corresponding Long value
335343
*/
336344
static fromStringStrict(str: string, unsigned?: boolean, radix?: number): Long {
337-
return Long.fromStringHelper(str, unsigned, radix, true);
345+
// remove leading zeros (for later string comparison and to make math faster)
346+
const cleanedStr = removeLeadingZeros(str);
347+
// doing this check outside of recursive function so cleanedStr value is consistent
348+
const result = Long.fromStringHelper(cleanedStr, unsigned, radix, true);
349+
if (result.toString(radix).toLowerCase() !== cleanedStr.toLowerCase()) {
350+
throw new BSONError(`Input: ${str} is not representable as a Long`);
351+
}
352+
return result;
338353
}
339354

340355
/**

src/utils/string_utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* @internal
3+
* Removes leading zeros from textual representation of a number.
4+
*/
5+
export function removeLeadingZeros(str: string): string {
6+
return !/[^0]+/.test(str)
7+
? str.replace(/^0+/, '0') // all zeros case
8+
: str[0] === '-'
9+
? str.replace(/^-0+/, '-') // negative number with leading zeros
10+
: str.replace(/^\+?0+/, '');
11+
}

test/node/long.test.ts

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -164,46 +164,73 @@ describe('Long', function () {
164164
});
165165
});
166166

167-
describe.only('static fromStringStrict()', function () {
167+
describe('static fromStringStrict()', function () {
168168
const successInputs = [
169-
['basic no alphabet low radix', '1236', 8],
170-
['radix does allow given alphabet letter', 'eEe', 15],
171-
['hexadecimal letters', '126073efbcdADEF', 16],
172-
['negative hexadecimal letters', '-126073efbcdADEF', 16]
169+
['basic no alphabet low radix', '1236', true, 8],
170+
['negative basic no alphabet low radix', '-1236', false, 8],
171+
['valid upper and lower case letters in string with radix > 10', 'eEe', true, 15],
172+
['hexadecimal letters', '126073efbcdADEF', true, 16],
173+
['negative hexadecimal letters', '-1267efbcdDEF', false, 16],
174+
['negative leading zeros', '-00000032', false, 15, '-32'],
175+
['leading zeros', '00000032', false, 15, '32'],
176+
['explicit positive leading zeros', '+00000032', false, 15, '32'],
177+
['max unsigned binary input', Long.MAX_UNSIGNED_VALUE.toString(2), true, 2],
178+
['max unsigned decimal input', Long.MAX_UNSIGNED_VALUE.toString(10), true, 10],
179+
['max unsigned hex input', Long.MAX_UNSIGNED_VALUE.toString(16), true, 16],
180+
['max signed binary input', Long.MAX_VALUE.toString(2), false, 2],
181+
['max signed decimal input', Long.MAX_VALUE.toString(10), false, 10],
182+
['max signed hex input', Long.MAX_VALUE.toString(16), false, 16],
183+
['min signed binary input', Long.MIN_VALUE.toString(2), false, 2],
184+
['min signed decimal input', Long.MIN_VALUE.toString(10), false, 10],
185+
['min signed hex input', Long.MIN_VALUE.toString(16), false, 16],
186+
['signed zero', '0', false, 10],
187+
['unsigned zero', '0', true, 10]
173188
];
174189

175190
const failureInputs = [
176-
['empty string','', 2],
177-
['non a-z or numeric string', '~~', 36],
178-
['alphabet in radix < 10', 'a', 4],
179-
['radix does not allow all alphabet letters', 'eee', 14]
191+
['empty string', '', true, 2],
192+
['invalid numbers in binary string', '234', true, 2],
193+
['non a-z or numeric string', '~~', true, 36],
194+
['alphabet in radix < 10', 'a', true, 9],
195+
['radix does not allow all alphabet letters', 'eee', 14],
196+
['over max unsigned binary input', Long.MAX_UNSIGNED_VALUE.toString(2) + '1', true, 2],
197+
['over max unsigned decimal input', Long.MAX_UNSIGNED_VALUE.toString(10) + '1', true, 10],
198+
['over max unsigned hex input', Long.MAX_UNSIGNED_VALUE.toString(16) + '1', true, 16],
199+
['over max signed binary input', Long.MAX_VALUE.toString(2) + '1', false, 2],
200+
['over max signed decimal input', Long.MAX_VALUE.toString(10) + '1', false, 10],
201+
['over max signed hex input', Long.MAX_VALUE.toString(16) + '1', false, 16],
202+
['under min signed binary input', Long.MIN_VALUE.toString(2) + '1', false, 2],
203+
['under min signed decimal input', Long.MIN_VALUE.toString(10) + '1', false, 10],
204+
['under min signed hex input', Long.MIN_VALUE.toString(16) + '1', false, 16]
180205
];
181206

182-
for (const [testName, str, radix] of successInputs) {
207+
for (const [testName, str, unsigned, radix, expectedStr] of successInputs) {
183208
context(`when the input is ${testName}`, () => {
184209
it(`should return a input string`, () => {
185-
expect(Long.fromStringStrict(str, true, radix).toString(radix)).to.equal(str.toLowerCase());
210+
expect(Long.fromStringStrict(str, unsigned, radix).toString(radix)).to.equal(
211+
expectedStr ?? str.toLowerCase()
212+
);
186213
});
187214
});
188215
}
189-
for (const [testName, str, radix] of failureInputs) {
216+
for (const [testName, str, unsigned, radix] of failureInputs) {
190217
context(`when the input is ${testName}`, () => {
191-
it(`should return false`, () => {
192-
expect(() => Long.fromStringStrict(str, true, radix)).to.throw(BSONError);
218+
it(`should throw BSONError`, () => {
219+
expect(() => Long.fromStringStrict(str, unsigned, radix)).to.throw(BSONError);
193220
});
194221
});
195222
}
196223
});
197224

198225
describe('static validateStringCharacters()', function () {
199226
const successInputs = [
200-
['multiple decimal points', '..', 30],
201-
['radix does not allow given alphabet letter', 'eEe', 15],
202-
['empty string','', 2],
227+
['radix does allows given alphabet letter', 'eEe', 15],
228+
['empty string', '', 2],
203229
['all possible hexadecimal characters', '12efabc689873dADCDEF', 16]
204230
];
205231

206232
const failureInputs = [
233+
['multiple decimal points', '..', 30],
207234
['non a-z or numeric string', '~~', 36],
208235
['alphabet in radix < 10', 'a', 4],
209236
['radix does not allow all alphabet letters', 'eee', 14]

test/node/release.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const REQUIRED_FILES = [
4848
'src/utils/byte_utils.ts',
4949
'src/utils/node_byte_utils.ts',
5050
'src/utils/number_utils.ts',
51+
'src/utils/string_utils.ts',
5152
'src/utils/web_byte_utils.ts',
5253
'src/utils/latin.ts',
5354
'src/validate_utf8.ts',

0 commit comments

Comments
 (0)