fix(errors): hide internal agent metadata
This commit is contained in:
parent
fa17c01c3b
commit
16920188ee
|
|
@ -2405,7 +2405,7 @@ async function loadObjects(options?: { allowCached?: boolean }) {
|
|||
void loadObjectStatistics(request, cacheWriteToken, cachedAt);
|
||||
} catch (e: any) {
|
||||
if (!objectBrowserRowsLoadGuard.isCurrent(request)) return;
|
||||
error.value = e?.message || String(e);
|
||||
error.value = translateBackendError(t, e);
|
||||
} finally {
|
||||
if (objectBrowserRowsLoadGuard.isCurrent(request)) finishObjectBrowserRowsLoad();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -720,7 +720,7 @@ async function toggle() {
|
|||
if (!wasExpanded) node.isExpanded = false;
|
||||
const errMsg = e?.message || String(e);
|
||||
if (errMsg.includes(CONNECTION_ATTEMPT_CANCELLED_MESSAGE)) return;
|
||||
toast(t("connection.connectFailed", { message: translateBackendError(t, errMsg) }), 5000);
|
||||
toast(t("connection.connectFailed", { message: translateBackendError(t, e) }), 5000);
|
||||
openDriverStoreForInstallError(errMsg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|||
import { describe, expect, it as test } from "vitest";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import { translateBackendError, type BackendErrorTranslate } from "@/i18n/backend-errors";
|
||||
import { BackendErrorException, normalizeBackendError } from "@/lib/backend/errorUtils";
|
||||
import { BackendErrorException, formatError, normalizeBackendError, sanitizeBackendErrorMessage } from "@/lib/backend/errorUtils";
|
||||
import en from "@/i18n/locales/en";
|
||||
import es from "@/i18n/locales/es";
|
||||
import it from "@/i18n/locales/it";
|
||||
|
|
@ -171,6 +171,23 @@ describe("backend error translation", () => {
|
|||
expect(translateBackendError(t, "some driver specific failure")).toBe("some driver specific failure");
|
||||
});
|
||||
|
||||
test("hides internal Agent error data from user-facing messages", () => {
|
||||
const t = translatorFor("zh-CN");
|
||||
const message = 'Agent RPC error (-1): driver: bad connection\nDBX_AGENT_ERROR_DATA:{"category":null,"retryable":null,"sessionDisposition":null,"stage":null,"operationOutcome":null,"agentSessionId":"e1a4d0a2907947b8adf31abb10c4dff9"}';
|
||||
const expected = "Agent RPC error (-1): driver: bad connection";
|
||||
|
||||
expect(sanitizeBackendErrorMessage(message)).toBe(expected);
|
||||
expect(formatError(new Error(message))).toBe(expected);
|
||||
expect(new BackendErrorException(message).message).toBe(expected);
|
||||
expect(translateBackendError(t, message)).toBe(expected);
|
||||
});
|
||||
|
||||
test("preserves marker-like database messages without valid internal data", () => {
|
||||
const message = "database returned\nDBX_AGENT_ERROR_DATA:not-json";
|
||||
|
||||
expect(sanitizeBackendErrorMessage(message)).toBe(message);
|
||||
});
|
||||
|
||||
test("normalizes Error and structural message objects before translation", () => {
|
||||
const t = translatorFor("zh-CN");
|
||||
const message = "file does not exist: /tmp/missing.sqlite";
|
||||
|
|
@ -213,6 +230,22 @@ describe("backend error translation", () => {
|
|||
expect(translateBackendError(t, error)).toBe(`${t(error.messageKey)}\n\n${error.detail}`);
|
||||
});
|
||||
|
||||
test("hides internal Agent error data from structured error details", () => {
|
||||
const t = translatorFor("zh-CN");
|
||||
const detail = 'driver: bad connection\nDBX_AGENT_ERROR_DATA:{"category":null,"agentSessionId":"session-1"}';
|
||||
const error = {
|
||||
version: 1,
|
||||
code: "DBX-JDBC-9001",
|
||||
messageKey: "backendErrors.jdbc.legacyFailure",
|
||||
messageParams: {},
|
||||
source: "jdbcAgentLegacy",
|
||||
operationOutcome: "unknown",
|
||||
detail,
|
||||
} as const;
|
||||
|
||||
expect(translateBackendError(t, error)).toBe(`${t(error.messageKey)}\n\ndriver: bad connection`);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["array params", { messageParams: ["execute"] }],
|
||||
["nested params", { messageParams: { stage: { name: "execute" } } }],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { normalizeBackendError, type BackendError } from "@/lib/backend/errorUtils";
|
||||
import { normalizeBackendError, sanitizeBackendErrorMessage, type BackendError } from "@/lib/backend/errorUtils";
|
||||
|
||||
/**
|
||||
* Minimal shape of a translate function, satisfied by both `useI18n().t` inside
|
||||
|
|
@ -115,15 +115,15 @@ const paramNames: Record<string, string | string[]> = {
|
|||
};
|
||||
|
||||
function backendErrorMessage(error: unknown): string {
|
||||
if (typeof error === "string") return error;
|
||||
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
|
||||
return String(error);
|
||||
if (typeof error === "string") return sanitizeBackendErrorMessage(error);
|
||||
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return sanitizeBackendErrorMessage(error.message);
|
||||
return sanitizeBackendErrorMessage(String(error));
|
||||
}
|
||||
|
||||
function translateStructuredBackendError(t: BackendErrorTranslate, error: BackendError): string {
|
||||
const translated = t(error.messageKey, error.messageParams);
|
||||
const summary = translated !== error.messageKey ? translated : t("backendErrors.unknown");
|
||||
const detail = error.detail?.trim();
|
||||
const detail = error.detail ? sanitizeBackendErrorMessage(error.detail).trim() : undefined;
|
||||
return detail && detail !== summary ? `${summary}\n\n${detail}` : summary;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,23 @@ export interface BackendError {
|
|||
helpUrl?: string;
|
||||
}
|
||||
|
||||
const AGENT_RPC_ERROR_DATA_MARKER = "\nDBX_AGENT_ERROR_DATA:";
|
||||
|
||||
export function sanitizeBackendErrorMessage(message: string): string {
|
||||
const markerIndex = message.lastIndexOf(AGENT_RPC_ERROR_DATA_MARKER);
|
||||
if (markerIndex < 0) return message;
|
||||
|
||||
const rawData = message.slice(markerIndex + AGENT_RPC_ERROR_DATA_MARKER.length).trim();
|
||||
try {
|
||||
const data: unknown = JSON.parse(rawData);
|
||||
if (!data || typeof data !== "object" || Array.isArray(data)) return message;
|
||||
} catch {
|
||||
return message;
|
||||
}
|
||||
|
||||
return message.slice(0, markerIndex).trimEnd();
|
||||
}
|
||||
|
||||
function isBackendError(value: unknown): value is BackendError {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const candidate = value as Record<string, unknown>;
|
||||
|
|
@ -56,8 +73,8 @@ export class BackendErrorException extends Error {
|
|||
|
||||
constructor(error: unknown) {
|
||||
const backendError = normalizeRawBackendError(error);
|
||||
const fallbackMessage = typeof error === "string" ? error : error instanceof Error ? error.message : "Backend request failed";
|
||||
super(backendError?.detail || fallbackMessage);
|
||||
const fallbackMessage = sanitizeBackendErrorMessage(typeof error === "string" ? error : error instanceof Error ? error.message : "Backend request failed");
|
||||
super(backendError?.detail ? sanitizeBackendErrorMessage(backendError.detail) : fallbackMessage);
|
||||
this.name = "BackendErrorException";
|
||||
this.backendError = backendError ?? {
|
||||
version: 1,
|
||||
|
|
@ -99,14 +116,14 @@ function normalizeRawBackendError(error: unknown): BackendError | null {
|
|||
*/
|
||||
export function formatError(e: unknown): string {
|
||||
const backendError = normalizeBackendError(e);
|
||||
if (backendError?.detail) return backendError.detail;
|
||||
if (backendError?.detail) return sanitizeBackendErrorMessage(backendError.detail);
|
||||
|
||||
if (e instanceof Error) {
|
||||
return e.message;
|
||||
return sanitizeBackendErrorMessage(e.message);
|
||||
}
|
||||
|
||||
if (typeof e === "string") {
|
||||
return e;
|
||||
return sanitizeBackendErrorMessage(e);
|
||||
}
|
||||
|
||||
if (e === null || e === undefined) {
|
||||
|
|
@ -117,13 +134,13 @@ export function formatError(e: unknown): string {
|
|||
if (typeof e === "object" && "message" in e) {
|
||||
const message = (e as { message: unknown }).message;
|
||||
if (typeof message === "string") {
|
||||
return message;
|
||||
return sanitizeBackendErrorMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: attempt to stringify
|
||||
try {
|
||||
return String(e);
|
||||
return sanitizeBackendErrorMessage(String(e));
|
||||
} catch {
|
||||
return "Unknown error occurred";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue