Skip to content

Commit 5a21889

Browse files
authoredApr 17, 2024··
feat(NODE-6087): add Int32.fromString method (#670)
1 parent eeab1e8 commit 5a21889

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed
 

‎src/int_32.ts

+37
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { BSONValue } from './bson_value';
2+
import { BSON_INT32_MAX, BSON_INT32_MIN } from './constants';
3+
import { BSONError } from './error';
24
import type { EJSONOptions } from './extended_json';
35
import { type InspectFn, defaultInspect } from './parser/utils';
46

@@ -32,6 +34,41 @@ export class Int32 extends BSONValue {
3234
this.value = +value | 0;
3335
}
3436

37+
/**
38+
* Attempt to create an Int32 type from string.
39+
*
40+
* This method will throw a BSONError on any string input that is not representable as an Int32.
41+
* Notably, this method will also throw on the following string formats:
42+
* - Strings in non-decimal formats (exponent notation, binary, hex, or octal digits)
43+
* - Strings non-numeric and non-leading sign characters (ex: '2.0', '24,000')
44+
* - Strings with leading and/or trailing whitespace
45+
*
46+
* Strings with leading zeros, however, are allowed.
47+
*
48+
* @param value - the string we want to represent as an int32.
49+
*/
50+
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
56+
57+
const coercedValue = Number(value);
58+
59+
if (BSON_INT32_MAX < coercedValue) {
60+
throw new BSONError(`Input: '${value}' is larger than the maximum value for Int32`);
61+
} else if (BSON_INT32_MIN > coercedValue) {
62+
throw new BSONError(`Input: '${value}' is smaller than the minimum value for Int32`);
63+
} else if (!Number.isSafeInteger(coercedValue)) {
64+
throw new BSONError(`Input: '${value}' is not a safe integer`);
65+
} else if (coercedValue.toString() !== cleanedValue) {
66+
// catch all case
67+
throw new BSONError(`Input: '${value}' is not a valid Int32 string`);
68+
}
69+
return new Int32(coercedValue);
70+
}
71+
3572
/**
3673
* Access the number value.
3774
*

‎test/node/int_32_tests.js

+47
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const BSON = require('../register-bson');
44
const Int32 = BSON.Int32;
5+
const BSONError = BSON.BSONError;
56

67
describe('Int32', function () {
78
context('Constructor', function () {
@@ -97,4 +98,50 @@ describe('Int32', function () {
9798
});
9899
}
99100
});
101+
102+
describe('fromString', () => {
103+
const acceptedInputs = [
104+
['Int32.max', '2147483647', 2147483647],
105+
['Int32.min', '-2147483648', -2147483648],
106+
['zero', '0', 0],
107+
['a string with non-leading consecutive zeros', '45000000', 45000000],
108+
['a string with zero with leading zeros', '000000', 0],
109+
['a string with positive leading zeros', '000000867', 867],
110+
['a string with explicity positive leading zeros', '+000000867', 867],
111+
['a string with negative leading zeros', '-00007', -7]
112+
];
113+
const errorInputs = [
114+
['Int32.max + 1', '2147483648', 'larger than the maximum value for Int32'],
115+
['Int32.min - 1', '-2147483649', 'smaller than the minimum value for Int32'],
116+
['positive integer with decimal', '2.0', 'not a valid Int32 string'],
117+
['zero with decimals', '0.0', 'not a valid Int32 string'],
118+
['negative zero', '-0', 'not a valid Int32 string'],
119+
['Infinity', 'Infinity', 'larger than the maximum value for Int32'],
120+
['-Infinity', '-Infinity', 'smaller than the minimum value for Int32'],
121+
['NaN', 'NaN', 'not a safe integer'],
122+
['a fraction', '2/3', 'not a safe integer'],
123+
['a string containing commas', '34,450', 'not a safe integer'],
124+
['a string in exponentiation notation', '1e1', 'not a valid Int32 string'],
125+
['a octal string', '0o1', 'not a valid Int32 string'],
126+
['a binary string', '0b1', 'not a valid Int32 string'],
127+
['a hexadecimal string', '0x1', 'not a valid Int32 string'],
128+
['a empty string', '', 'not a valid Int32 string'],
129+
['a leading and trailing whitespace', ' 89 ', 'not a valid Int32 string']
130+
];
131+
132+
for (const [testName, value, expectedInt32] of acceptedInputs) {
133+
context(`when the input is ${testName}`, () => {
134+
it(`should successfully return an Int32 representation`, () => {
135+
expect(Int32.fromString(value).value).to.equal(expectedInt32);
136+
});
137+
});
138+
}
139+
for (const [testName, value, expectedErrMsg] of errorInputs) {
140+
context(`when the input is ${testName}`, () => {
141+
it(`should throw an error containing '${expectedErrMsg}'`, () => {
142+
expect(() => Int32.fromString(value)).to.throw(BSONError, expectedErrMsg);
143+
});
144+
});
145+
}
146+
});
100147
});

0 commit comments

Comments
 (0)
Please sign in to comment.