From 26a8fd410caa7f9d7012f210169f6f7f33050258 Mon Sep 17 00:00:00 2001 From: Dimitris Marlagkoutsos Date: Thu, 19 Sep 2024 10:44:48 +0100 Subject: [PATCH] fix(cli): Improve number parsing --- packages/cli/src/commands/adopt.js | 9 +++-- packages/cli/src/commands/reject.js | 5 +-- packages/cli/src/commands/resolve.js | 5 +-- packages/cli/src/number-parse.js | 17 +++++++++ packages/cli/test/number-parse.test.js | 53 ++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 packages/cli/src/number-parse.js create mode 100644 packages/cli/test/number-parse.test.js 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..a9ad403af6 --- /dev/null +++ b/packages/cli/src/number-parse.js @@ -0,0 +1,17 @@ +/** + * 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 => { + if (typeof input === 'number' && !Number.isNaN(input)) { + return input; + } + + if (typeof input === 'string' && /^-?\d+(\.\d+)?$/.test(input.trim())) { + return Number(input); + } + + throw new Error(`Invalid number: ${input}`); +}; diff --git a/packages/cli/test/number-parse.test.js b/packages/cli/test/number-parse.test.js new file mode 100644 index 0000000000..c89cce0bb5 --- /dev/null +++ b/packages/cli/test/number-parse.test.js @@ -0,0 +1,53 @@ +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 +test('returns the number when input is a valid numeric string', t => { + t.is(parseNumber('42'), 42); + t.is(parseNumber('-42'), -42); + t.is(parseNumber('3.14'), 3.14); + t.is(parseNumber('-3.14'), -3.14); + t.is(parseNumber(' 42 '), 42); +}); + +// Test for invalid string input +test('throws an error when input is not a valid numeric string', t => { + const error = t.throws(() => parseNumber('abc')); + t.is(error.message, 'Invalid number: abc'); + + const error2 = t.throws(() => parseNumber('42abc')); + t.is(error2.message, 'Invalid number: 42abc'); + + const error3 = t.throws(() => parseNumber('')); + t.is(error3.message, 'Invalid number: '); + + const error4 = t.throws(() => parseNumber(' ')); + t.is(error4.message, 'Invalid number: '); + + const error5 = t.throws(() => parseNumber('42..42')); + t.is(error5.message, 'Invalid number: 42..42'); +}); + +// 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: undefined'); + + const error3 = t.throws(() => parseNumber({})); + t.is(error3.message, 'Invalid number: [object Object]'); + + const error4 = t.throws(() => parseNumber([])); + t.is(error4.message, 'Invalid number: '); +});