diff --git a/packages/cli/src/commands/adopt.js b/packages/cli/src/commands/adopt.js index ddde44a584..3503c342e3 100644 --- a/packages/cli/src/commands/adopt.js +++ b/packages/cli/src/commands/adopt.js @@ -3,6 +3,7 @@ import os from 'os'; import { E } from '@endo/far'; import { withEndoAgent } from '../context.js'; import { parsePetNamePath } from '../pet-name.js'; +import { parseNumber } from '../number-parse.js'; export const adoptCommand = async ({ messageNumberText, @@ -11,7 +12,9 @@ export const adoptCommand = async ({ agentNames, }) => withEndoAgent(agentNames, { os, process }, async ({ agent }) => { - // TODO less bad number parsing. - const messageNumber = Number(messageNumberText); - await E(agent).adopt(messageNumber, edgeName, parsePetNamePath(name)); + await E(agent).adopt( + parseNumber(messageNumberText), + edgeName, + parsePetNamePath(name), + ); }); diff --git a/packages/cli/src/commands/reject.js b/packages/cli/src/commands/reject.js index c8e952d15a..2e8ef2d885 100644 --- a/packages/cli/src/commands/reject.js +++ b/packages/cli/src/commands/reject.js @@ -2,6 +2,7 @@ import os from 'os'; import { E } from '@endo/far'; import { withEndoAgent } from '../context.js'; +import { parseNumber } from '../number-parse.js'; export const rejectCommand = async ({ requestNumberText, @@ -9,7 +10,5 @@ export const rejectCommand = async ({ agentNames, }) => withEndoAgent(agentNames, { os, process }, async ({ agent }) => { - // TODO less bad number parsing. - const requestNumber = Number(requestNumberText); - await E(agent).reject(requestNumber, message); + await E(agent).reject(parseNumber(requestNumberText), message); }); diff --git a/packages/cli/src/commands/resolve.js b/packages/cli/src/commands/resolve.js index 7704e59612..941b1a23bf 100644 --- a/packages/cli/src/commands/resolve.js +++ b/packages/cli/src/commands/resolve.js @@ -2,6 +2,7 @@ import os from 'os'; import { E } from '@endo/far'; import { withEndoAgent } from '../context.js'; +import { parseNumber } from '../number-parse.js'; export const resolveCommand = async ({ requestNumberText, @@ -9,7 +10,5 @@ export const resolveCommand = async ({ agentNames, }) => withEndoAgent(agentNames, { os, process }, async ({ agent }) => { - // TODO less bad number parsing. - const requestNumber = Number(requestNumberText); - await E(agent).resolve(requestNumber, resolutionName); + await E(agent).resolve(parseNumber(requestNumberText), resolutionName); }); diff --git a/packages/cli/src/number-parse.js b/packages/cli/src/number-parse.js new file mode 100644 index 0000000000..37f5cedb2d --- /dev/null +++ b/packages/cli/src/number-parse.js @@ -0,0 +1,15 @@ +/** + * Parses the input and returns it as a number if it's valid, otherwise throws error. + * + * @param {string | number} input + * @returns {number | undefined} + */ +export const parseNumber = (input = '') => { + const result = /[0-9]/.test(input) ? Number(input) : NaN; + + if (Number.isNaN(result)) { + throw new Error(`Invalid number: ${input}`); + } + + return result; +}; diff --git a/packages/cli/test/number-parse.test.js b/packages/cli/test/number-parse.test.js new file mode 100644 index 0000000000..2013c78cf7 --- /dev/null +++ b/packages/cli/test/number-parse.test.js @@ -0,0 +1,67 @@ +import test from 'ava'; +import { parseNumber } from '../src/number-parse.js'; + +// Test for valid number input +test('returns the number itself if input is a valid number', t => { + t.is(parseNumber(42), 42); + t.is(parseNumber(-42), -42); + t.is(parseNumber(0), 0); + t.is(parseNumber(3.14), 3.14); + t.is(parseNumber(-3.14), -3.14); +}); + +// Test for valid string input (includes binary, octal and hexadecimal) +test('returns the number when input is a valid numeric string', t => { + t.is(parseNumber('42'), 42); + t.is(parseNumber('-40.0'), -40.0); + t.is(parseNumber('.5'), 0.5); + t.is(parseNumber('1.337e3'), 1337); + t.is(parseNumber(' +1 '), 1); + t.is(parseNumber('0B111'), 7); + t.is(parseNumber('0o040'), 32); + t.is(parseNumber('0xf00'), 3840); + t.is(parseNumber(' -40.0 '), -40.0); + // Test for binary, octal and hexadecimal + t.is(parseNumber('0B111'), 7); + t.is(parseNumber('0o040'), 32); + t.is(parseNumber('0xf00'), 3840); +}); + +// Test for invalid string input +test('throws an error when input is not a valid numeric string', t => { + const error = t.throws(() => parseNumber('f00')); + t.is(error.message, 'Invalid number: f00'); + + const error2 = t.throws(() => parseNumber('NaN')); + t.is(error2.message, 'Invalid number: NaN'); + + const error3 = t.throws(() => parseNumber('Infinity')); + t.is(error3.message, 'Invalid number: Infinity'); + + const error4 = t.throws(() => parseNumber('7up')); + t.is(error4.message, 'Invalid number: 7up'); + + const error5 = t.throws(() => parseNumber(' ')); // Unicode character + t.is(error5.message, 'Invalid number:  '); + + const error6 = t.throws(() => parseNumber('')); + t.is(error6.message, 'Invalid number: '); + + const error7 = t.throws(() => parseNumber(' ')); + t.is(error7.message, 'Invalid number: '); +}); + +// Test for invalid input +test('throws an error when input is not a valid number', t => { + const error1 = t.throws(() => parseNumber(null)); + t.is(error1.message, 'Invalid number: null'); + + const error2 = t.throws(() => parseNumber(undefined)); + t.is(error2.message, 'Invalid number: '); + + const error3 = t.throws(() => parseNumber({})); + t.is(error3.message, 'Invalid number: [object Object]'); + + const error4 = t.throws(() => parseNumber([])); + t.is(error4.message, 'Invalid number: '); +});