refactor(utils): add safeExtractMessageAndStackFromError util to remove code duplication

This commit is contained in:
Panagiotis Papadopoulos 2025-03-08 17:07:25 +01:00
parent e20b662ea7
commit ecf1a0e4ad
9 changed files with 44 additions and 17 deletions

View File

@ -9,6 +9,7 @@ import log from "../../services/log.js";
import NotFoundError from "../../errors/not_found_error.js";
import type { Request, Response } from "express";
import ValidationError from "../../errors/validation_error.js";
import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
function exportBranch(req: Request, res: Response) {
const { branchId, type, format, version, taskId } = req.params;
@ -38,10 +39,11 @@ function exportBranch(req: Request, res: Response) {
throw new NotFoundError(`Unrecognized export format '${format}'`);
}
} catch (e: unknown) {
const message = `Export failed with following error: '${(e instanceof Error) ? e.message : e}'. More details might be in the logs.`;
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
const message = `Export failed with following error: '${errMessage}'. More details might be in the logs.`;
taskContext.reportError(message);
log.error((e instanceof Error) ? message + e.stack : message);
log.error(errMessage + errStack);
res.setHeader("Content-Type", "text/plain").status(500).send(message);
}

View File

@ -13,6 +13,7 @@ import TaskContext from "../../services/task_context.js";
import ValidationError from "../../errors/validation_error.js";
import type { Request } from "express";
import type BNote from "../../becca/entities/bnote.js";
import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
async function importNotesToBranch(req: Request) {
const { parentNoteId } = req.params;
@ -69,7 +70,7 @@ async function importNotesToBranch(req: Request) {
note = await singleImportService.importSingleFile(taskContext, file, parentNote);
}
} catch (e: unknown) {
const [errMessage, errStack] = e instanceof Error ? [e.message, e.stack] : ["Unknown Error", undefined];
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
const message = `Import failed with following error: '${errMessage}'. More details might be in the logs.`;
taskContext.reportError(message);
@ -122,7 +123,7 @@ async function importAttachmentsToNote(req: Request) {
try {
await singleImportService.importAttachment(taskContext, file, parentNote);
} catch (e: unknown) {
const [errMessage, errStack] = e instanceof Error ? [e.message, e.stack] : ["Unknown Error", undefined];
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
const message = `Import failed with following error: '${errMessage}'. More details might be in the logs.`;
taskContext.reportError(message);

View File

@ -6,6 +6,7 @@ import becca from "../../becca/becca.js";
import syncService from "../../services/sync.js";
import sql from "../../services/sql.js";
import type { Request } from "express";
import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
interface ScriptBody {
script: string;
@ -34,9 +35,10 @@ async function exec(req: Request) {
maxEntityChangeId: syncService.getMaxEntityChangeId()
};
} catch (e: unknown) {
const [errMessage] = safeExtractMessageAndStackFromError(e);
return {
success: false,
error: (e instanceof Error) ? e.message : "Unknown Error"
error: errMessage
};
}
}

View File

@ -4,6 +4,7 @@ import sql from "../../services/sql.js";
import becca from "../../becca/becca.js";
import type { Request } from "express";
import ValidationError from "../../errors/validation_error.js";
import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
function getSchema() {
const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
@ -57,9 +58,10 @@ function execute(req: Request) {
results
};
} catch (e: unknown) {
const [errMessage] = safeExtractMessageAndStackFromError(e);
return {
success: false,
error: (e instanceof Error) ? e.message : "Unknown Error"
error: errMessage
};
}
}

View File

@ -9,7 +9,7 @@ import optionService from "../../services/options.js";
import contentHashService from "../../services/content_hash.js";
import log from "../../services/log.js";
import syncOptions from "../../services/sync_options.js";
import utils from "../../services/utils.js";
import utils, { safeExtractMessageAndStackFromError } from "../../services/utils.js";
import ws from "../../services/ws.js";
import type { Request } from "express";
import type { EntityChange } from "../../services/entity_changes_interface.js";
@ -31,9 +31,10 @@ async function testSync() {
return { success: true, message: t("test_sync.successful") };
} catch (e: unknown) {
const [errMessage] = safeExtractMessageAndStackFromError(e);
return {
success: false,
error: (e instanceof Error) ? e.message : "Unknown Error"
error: errMessage
};
}
}

View File

@ -5,6 +5,7 @@ import cls from "../services/cls.js";
import sql from "../services/sql.js";
import becca from "../becca/becca.js";
import type { Request, Response, Router } from "express";
import { safeExtractMessageAndStackFromError } from "../services/utils.js";
function handleRequest(req: Request, res: Response) {
// express puts content after first slash into 0 index element
@ -26,7 +27,7 @@ function handleRequest(req: Request, res: Response) {
try {
match = path.match(regex);
} catch (e: unknown) {
const [errMessage, errStack] = e instanceof Error ? [e.message, e.stack] : ["Unknown Error", undefined];
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
log.error(`Testing path for label '${attr.attributeId}', regex '${attr.value}' failed with error: ${errMessage}, stack: ${errStack}`);
continue;
}
@ -47,10 +48,8 @@ function handleRequest(req: Request, res: Response) {
res
});
} catch (e: unknown) {
const [errMessage, errStack] = e instanceof Error ? [e.message, e.stack] : ["Unknown Error", undefined];
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
log.error(`Custom handler '${note.noteId}' failed with: ${errMessage}, ${errStack}`);
res.setHeader("Content-Type", "text/plain").status(500).send(errMessage);
}
} else if (attr.name === "customResourceProvider") {

View File

@ -1,6 +1,6 @@
"use strict";
import { isElectron } from "../services/utils.js";
import { isElectron, safeExtractMessageAndStackFromError } from "../services/utils.js";
import multer from "multer";
import log from "../services/log.js";
import express from "express";
@ -494,10 +494,7 @@ function handleResponse(resultHandler: ApiResultHandler, req: express.Request, r
}
function handleException(e: unknown | Error, method: HttpMethod, path: string, res: express.Response) {
const [errMessage, errStack]: [message: string, stack: string] = (e instanceof Error)
? [e.message, e.stack || "No Stack Trace"]
: ["Unknown Error", "No Stack Trace"];
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
log.error(`${method} ${path} threw exception: '${errMessage}', stack: ${errStack}`);

View File

@ -500,6 +500,23 @@ describe("#isDev", () => {
});
});
describe("#safeExtractMessageAndStackFromError", () => {
it("should correctly extract the message and stack property if it gets passed an instance of an Error", () => {
const testMessage = "Test Message";
const testError = new Error(testMessage);
const actual = utils.safeExtractMessageAndStackFromError(testError);
expect(actual[0]).toBe(testMessage);
expect(actual[1]).not.toBeUndefined();
});
it("should use the fallback 'Unknown Error' message, if it gets passed anything else than an instance of an Error", () => {
const testNonError = "this is not an instance of an Error, but JS technically allows us to throw this anyways";
const actual = utils.safeExtractMessageAndStackFromError(testNonError);
expect(actual[0]).toBe("Unknown Error");
expect(actual[1]).toBeUndefined();
});
})
describe("#formatDownloadTitle", () => {
//prettier-ignore
const testCases: [fnValue: Parameters<typeof utils.formatDownloadTitle>, expectedValue: ReturnType<typeof utils.formatDownloadTitle>][] = [

View File

@ -363,6 +363,11 @@ export function processStringOrBuffer(data: string | Buffer | null) {
}
}
export function safeExtractMessageAndStackFromError(err: unknown) {
return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const;
}
export default {
compareVersions,
crash,
@ -393,6 +398,7 @@ export default {
removeDiacritic,
removeTextFileExtension,
replaceAll,
safeExtractMessageAndStackFromError,
sanitizeSqlIdentifier,
stripTags,
timeLimit,