Skip to content

Commit

Permalink
feat(NODE-6087): add Int32.fromString method (#670)
Browse files Browse the repository at this point in the history
  • Loading branch information
aditi-khare-mongoDB committed Apr 17, 2024
1 parent eeab1e8 commit 5a21889
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
37 changes: 37 additions & 0 deletions src/int_32.ts
@@ -1,4 +1,6 @@
import { BSONValue } from './bson_value';
import { BSON_INT32_MAX, BSON_INT32_MIN } from './constants';
import { BSONError } from './error';
import type { EJSONOptions } from './extended_json';
import { type InspectFn, defaultInspect } from './parser/utils';

Expand Down Expand Up @@ -32,6 +34,41 @@ export class Int32 extends BSONValue {
this.value = +value | 0;
}

/**
* Attempt to create an Int32 type from string.
*
* This method will throw a BSONError on any string input that is not representable as an Int32.
* Notably, this method will also throw on the following string formats:
* - Strings in non-decimal formats (exponent notation, binary, hex, or octal digits)
* - Strings non-numeric and non-leading sign characters (ex: '2.0', '24,000')
* - Strings with leading and/or trailing whitespace
*
* Strings with leading zeros, however, are allowed.
*
* @param value - the string we want to represent as an int32.
*/
static fromString(value: string): Int32 {
const cleanedValue = !/[^0]+/.test(value)
? value.replace(/^0+/, '0') // all zeros case
: value[0] === '-'
? value.replace(/^-0+/, '-') // negative number with leading zeros
: value.replace(/^\+?0+/, ''); // positive number with leading zeros

const coercedValue = Number(value);

if (BSON_INT32_MAX < coercedValue) {
throw new BSONError(`Input: '${value}' is larger than the maximum value for Int32`);
} else if (BSON_INT32_MIN > coercedValue) {
throw new BSONError(`Input: '${value}' is smaller than the minimum value for Int32`);
} else if (!Number.isSafeInteger(coercedValue)) {
throw new BSONError(`Input: '${value}' is not a safe integer`);
} else if (coercedValue.toString() !== cleanedValue) {
// catch all case
throw new BSONError(`Input: '${value}' is not a valid Int32 string`);
}
return new Int32(coercedValue);
}

/**
* Access the number value.
*
Expand Down
47 changes: 47 additions & 0 deletions test/node/int_32_tests.js
Expand Up @@ -2,6 +2,7 @@

const BSON = require('../register-bson');
const Int32 = BSON.Int32;
const BSONError = BSON.BSONError;

describe('Int32', function () {
context('Constructor', function () {
Expand Down Expand Up @@ -97,4 +98,50 @@ describe('Int32', function () {
});
}
});

describe('fromString', () => {
const acceptedInputs = [
['Int32.max', '2147483647', 2147483647],
['Int32.min', '-2147483648', -2147483648],
['zero', '0', 0],
['a string with non-leading consecutive zeros', '45000000', 45000000],
['a string with zero with leading zeros', '000000', 0],
['a string with positive leading zeros', '000000867', 867],
['a string with explicity positive leading zeros', '+000000867', 867],
['a string with negative leading zeros', '-00007', -7]
];
const errorInputs = [
['Int32.max + 1', '2147483648', 'larger than the maximum value for Int32'],
['Int32.min - 1', '-2147483649', 'smaller than the minimum value for Int32'],
['positive integer with decimal', '2.0', 'not a valid Int32 string'],
['zero with decimals', '0.0', 'not a valid Int32 string'],
['negative zero', '-0', 'not a valid Int32 string'],
['Infinity', 'Infinity', 'larger than the maximum value for Int32'],
['-Infinity', '-Infinity', 'smaller than the minimum value for Int32'],
['NaN', 'NaN', 'not a safe integer'],
['a fraction', '2/3', 'not a safe integer'],
['a string containing commas', '34,450', 'not a safe integer'],
['a string in exponentiation notation', '1e1', 'not a valid Int32 string'],
['a octal string', '0o1', 'not a valid Int32 string'],
['a binary string', '0b1', 'not a valid Int32 string'],
['a hexadecimal string', '0x1', 'not a valid Int32 string'],
['a empty string', '', 'not a valid Int32 string'],
['a leading and trailing whitespace', ' 89 ', 'not a valid Int32 string']
];

for (const [testName, value, expectedInt32] of acceptedInputs) {
context(`when the input is ${testName}`, () => {
it(`should successfully return an Int32 representation`, () => {
expect(Int32.fromString(value).value).to.equal(expectedInt32);
});
});
}
for (const [testName, value, expectedErrMsg] of errorInputs) {
context(`when the input is ${testName}`, () => {
it(`should throw an error containing '${expectedErrMsg}'`, () => {
expect(() => Int32.fromString(value)).to.throw(BSONError, expectedErrMsg);
});
});
}
});
});

0 comments on commit 5a21889

Please sign in to comment.