From 99c31f2c12477dec85f33e4702dd6d13ba89ca81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Sch=C3=B6nb=C3=A4chler?= <42278642+schoero@users.noreply.github.com> Date: Sun, 1 Sep 2024 19:16:39 +0200 Subject: [PATCH] refactor: add `ValidationError.code` --- src/pdf/table.ts | 1 - src/shared/errors.test.ts | 15 +++++++++++ src/shared/errors.ts | 20 ++++++++++++++- tests/integration/validation-error.test.ts | 29 ++++++++++++++++++++++ tests/utils/errors.test.ts | 15 +++++++++++ tests/utils/errors.ts | 12 +++++++++ 6 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/shared/errors.test.ts create mode 100644 tests/integration/validation-error.test.ts create mode 100644 tests/utils/errors.test.ts create mode 100644 tests/utils/errors.ts diff --git a/src/pdf/table.ts b/src/pdf/table.ts index ff41266..9ac1caf 100644 --- a/src/pdf/table.ts +++ b/src/pdf/table.ts @@ -26,7 +26,6 @@ export interface PDFTable { /** Horizontal start position of the table. */ } - export interface PDFRow { /** Table columns. */ columns: PDFColumn[]; diff --git a/src/shared/errors.test.ts b/src/shared/errors.test.ts new file mode 100644 index 0000000..8715bcc --- /dev/null +++ b/src/shared/errors.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from "vitest"; + +import { getErrorCodeByMessage, ValidationErrors } from "swissqrbill:errors"; +import { } from "swissqrbill:shared:cleaner.js"; + + +describe("getErrorCodeByMessage", () => { + + it("should return the correct error code", () => { + const key = "ACCOUNT_IS_QR_IBAN_BUT_REFERENCE_IS_MISSING"; + expect(getErrorCodeByMessage(ValidationErrors[key])).toBe(key); + }); + + +}); diff --git a/src/shared/errors.ts b/src/shared/errors.ts index 5cb8900..e763fa9 100644 --- a/src/shared/errors.ts +++ b/src/shared/errors.ts @@ -1,15 +1,33 @@ +/** A {@link ValidationError} is thrown when the data provided to swissqrbill is invalid. */ export class ValidationError extends Error { - constructor(message: string, params?: { [name: string]: string; }) { + + /** A stable error code that can be used to identify the error programmatically. */ + public code: keyof typeof ValidationErrors; + + /** @internal */ + constructor(message: ValidationErrors, params?: { [name: string]: string; }) { const messageWithParams = params ? resolveMessageParams(message, params) : message; super(messageWithParams); + this.name = "ValidationError"; + this.code = getErrorCodeByMessage(message); + } } +/** @internal */ +export function getErrorCodeByMessage(message: string): keyof typeof ValidationErrors { + const errorCodes = Object.keys(ValidationErrors); + const errorCode = errorCodes.find(key => ValidationErrors[key] === message); + + return errorCode as keyof typeof ValidationErrors; +} + +/** @internal */ export function resolveMessageParams(message: string, params: { [name: string]: string; }): string { return Object.entries(params).reduce((message, [key, value]) => { return message.replace(`{${key}}`, value); diff --git a/tests/integration/validation-error.test.ts b/tests/integration/validation-error.test.ts new file mode 100644 index 0000000..f55b19d --- /dev/null +++ b/tests/integration/validation-error.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; + +import { ValidationError, ValidationErrors } from "swissqrbill:errors"; +import { cleanData } from "swissqrbill:shared:cleaner"; +import { validateData } from "swissqrbill:shared:validator"; +import { missingCreditor } from "swissqrbill:tests:data/invalid-data"; +import { getValidationError } from "swissqrbill:tests:utils/errors"; + + +describe("errors", () => { + + it("should be of instance 'Error' and 'ValidationError'", async () => { + expect(getValidationError(() => validateData(cleanData(missingCreditor)))).toBeInstanceOf(Error); + expect(getValidationError(() => validateData(cleanData(missingCreditor)))).toBeInstanceOf(ValidationError); + }); + + it("should have an error code", async () => { + const code = "CREDITOR_IS_UNDEFINED"; + expect(getValidationError(() => validateData(cleanData(missingCreditor)))?.code).toBeDefined(); + expect(getValidationError(() => validateData(cleanData(missingCreditor)))?.code).toBe(code); + }); + + it("should have an error message", async () => { + const code = "CREDITOR_IS_UNDEFINED"; + expect(getValidationError(() => validateData(cleanData(missingCreditor)))?.message).toBeDefined(); + expect(getValidationError(() => validateData(cleanData(missingCreditor)))?.message).toBe(ValidationErrors[code]); + }); + +}); diff --git a/tests/utils/errors.test.ts b/tests/utils/errors.test.ts new file mode 100644 index 0000000..31afda6 --- /dev/null +++ b/tests/utils/errors.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from "vitest"; + +import { ValidationError, ValidationErrors } from "swissqrbill:errors"; + +import { getValidationError } from "./errors"; + + +describe("error", () => { + + it("should return the caught error", () => { + expect(getValidationError(() => { throw new ValidationError(ValidationErrors.ACCOUNT_LENGTH_IS_INVALID); })).toBeInstanceOf(Error); + expect(getValidationError(() => { throw new ValidationError(ValidationErrors.ACCOUNT_LENGTH_IS_INVALID); })?.message).toBe(ValidationErrors.ACCOUNT_LENGTH_IS_INVALID); + }); + +}); diff --git a/tests/utils/errors.ts b/tests/utils/errors.ts new file mode 100644 index 0000000..e669f49 --- /dev/null +++ b/tests/utils/errors.ts @@ -0,0 +1,12 @@ +import { ValidationError } from "swissqrbill:errors"; + + +export function getValidationError(fn: (...params: unknown[]) => unknown): ValidationError | undefined { + try { + fn(); + } catch (error){ + if(error instanceof ValidationError){ + return error; + } + } +}