From 83ca78436c66e71cc2f5d7fa246ad73f706fb9da Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Sat, 7 Jun 2025 16:53:43 +1000 Subject: [PATCH] Validate user input amounts and fail gracefully --- src/components/BalanceAssertionEditor.vue | 36 +++++++++++++-- src/components/TransactionEditor.vue | 14 +++++- src/db.ts | 8 +++- src/plugins/austax/CGTAdjustmentEditor.vue | 45 +++++++++++++++++-- .../austax/MultiNewCGTAdjustmentView.vue | 37 +++++++++++++-- 5 files changed, 126 insertions(+), 14 deletions(-) diff --git a/src/components/BalanceAssertionEditor.vue b/src/components/BalanceAssertionEditor.vue index 1ad2801..75b657d 100644 --- a/src/components/BalanceAssertionEditor.vue +++ b/src/components/BalanceAssertionEditor.vue @@ -47,14 +47,26 @@ + +
+
+
+ +
+
+

{{ error }}

+
+
+
diff --git a/src/components/TransactionEditor.vue b/src/components/TransactionEditor.vue index 732a284..1e77895 100644 --- a/src/components/TransactionEditor.vue +++ b/src/components/TransactionEditor.vue @@ -109,7 +109,7 @@ import { ref } from 'vue'; - import { DT_FORMAT, Posting, Transaction, db, deserialiseAmount } from '../db.ts'; + import { DT_FORMAT, DeserialiseAmountError, Posting, Transaction, db, deserialiseAmount } from '../db.ts'; import ComboBoxAccounts from './ComboBoxAccounts.vue'; interface EditingPosting { @@ -155,7 +155,17 @@ ); for (const posting of transaction.postings) { - const amount_abs = deserialiseAmount(posting.amount_abs); + let amount_abs; + try { + amount_abs = deserialiseAmount(posting.amount_abs); + } catch (err) { + if (err instanceof DeserialiseAmountError) { + error.value = err.message; + return; + } else { + throw err; + } + } newTransaction.postings.push({ id: posting.id, diff --git a/src/db.ts b/src/db.ts index 4f178bc..cff148c 100644 --- a/src/db.ts +++ b/src/db.ts @@ -176,7 +176,8 @@ export function deserialiseAmount(amount: string): { quantity: number, commodity // Default commodity const quantity = Math.round(parseFloat(amount) * factor) - if (!Number.isSafeInteger(quantity)) { throw new Error('Quantity not representable by safe integer'); } + if (Number.isNaN(quantity)) { throw new DeserialiseAmountError('Invalid quantity: ' + amount); } + if (!Number.isSafeInteger(quantity)) { throw new DeserialiseAmountError('Quantity not representable by safe integer: ' + amount); } return { 'quantity': quantity, @@ -189,7 +190,8 @@ export function deserialiseAmount(amount: string): { quantity: number, commodity const quantityStr = amount.substring(0, amount.indexOf(' ')); const quantity = Math.round(parseFloat(quantityStr) * factor) - if (!Number.isSafeInteger(quantity)) { throw new Error('Quantity not representable by safe integer'); } + if (Number.isNaN(quantity)) { throw new DeserialiseAmountError('Invalid quantity: ' + amount); } + if (!Number.isSafeInteger(quantity)) { throw new DeserialiseAmountError('Quantity not representable by safe integer: ' + amount); } const commodity = amount.substring(amount.indexOf(' ') + 1); @@ -199,6 +201,8 @@ export function deserialiseAmount(amount: string): { quantity: number, commodity }; } +export class DeserialiseAmountError extends Error {} + // Type definitions export class Transaction { diff --git a/src/plugins/austax/CGTAdjustmentEditor.vue b/src/plugins/austax/CGTAdjustmentEditor.vue index 8c71f8f..5174caf 100644 --- a/src/plugins/austax/CGTAdjustmentEditor.vue +++ b/src/plugins/austax/CGTAdjustmentEditor.vue @@ -60,14 +60,27 @@ + +
+
+
+ +
+
+

{{ error }}

+
+
+