refactor: replace error handling with serverErrorMessage utility and checks for if error is ConfigInvalidError (#14685)
This commit is contained in:
@@ -36,6 +36,7 @@ import type { ProjectMeta } from "./global-sync/types"
|
|||||||
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
|
||||||
import { sanitizeProject } from "./global-sync/utils"
|
import { sanitizeProject } from "./global-sync/utils"
|
||||||
import { usePlatform } from "./platform"
|
import { usePlatform } from "./platform"
|
||||||
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
@@ -51,11 +52,6 @@ type GlobalStore = {
|
|||||||
reload: undefined | "pending" | "complete"
|
reload: undefined | "pending" | "complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
function errorMessage(error: unknown) {
|
|
||||||
if (error instanceof Error && error.message) return error.message
|
|
||||||
if (typeof error === "string" && error) return error
|
|
||||||
return "Unknown error"
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGlobalSync() {
|
function createGlobalSync() {
|
||||||
const globalSDK = useGlobalSDK()
|
const globalSDK = useGlobalSDK()
|
||||||
@@ -207,8 +203,9 @@ function createGlobalSync() {
|
|||||||
console.error("Failed to load sessions", err)
|
console.error("Failed to load sessions", err)
|
||||||
const project = getFilename(directory)
|
const project = getFilename(directory)
|
||||||
showToast({
|
showToast({
|
||||||
|
variant: "error",
|
||||||
title: language.t("toast.session.listFailed.title", { project }),
|
title: language.t("toast.session.listFailed.title", { project }),
|
||||||
description: errorMessage(err),
|
description: formatServerError(err),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { batch } from "solid-js"
|
|||||||
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
import { reconcile, type SetStoreFunction, type Store } from "solid-js/store"
|
||||||
import type { State, VcsCache } from "./types"
|
import type { State, VcsCache } from "./types"
|
||||||
import { cmp, normalizeProviderList } from "./utils"
|
import { cmp, normalizeProviderList } from "./utils"
|
||||||
|
import { formatServerError } from "@/utils/server-errors"
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
ready: boolean
|
ready: boolean
|
||||||
@@ -133,8 +134,11 @@ export async function bootstrapDirectory(input: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to bootstrap instance", err)
|
console.error("Failed to bootstrap instance", err)
|
||||||
const project = getFilename(input.directory)
|
const project = getFilename(input.directory)
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
showToast({
|
||||||
showToast({ title: `Failed to reload ${project}`, description: message })
|
variant: "error",
|
||||||
|
title: `Failed to reload ${project}`,
|
||||||
|
description: formatServerError(err)
|
||||||
|
})
|
||||||
input.setStore("status", "partial")
|
input.setStore("status", "partial")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
69
packages/app/src/utils/server-errors.test.ts
Normal file
69
packages/app/src/utils/server-errors.test.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import type { ConfigInvalidError } from "./server-errors"
|
||||||
|
import { formatServerError, parseReabaleConfigInvalidError } from "./server-errors"
|
||||||
|
|
||||||
|
describe("parseReabaleConfigInvalidError", () => {
|
||||||
|
test("formats issues with file path", () => {
|
||||||
|
const error = {
|
||||||
|
name: "ConfigInvalidError",
|
||||||
|
data: {
|
||||||
|
path: "opencode.config.ts",
|
||||||
|
issues: [
|
||||||
|
{ path: ["settings", "host"], message: "Required" },
|
||||||
|
{ path: ["mode"], message: "Invalid" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} satisfies ConfigInvalidError
|
||||||
|
|
||||||
|
const result = parseReabaleConfigInvalidError(error)
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
["Invalid configuration", "opencode.config.ts", "settings.host: Required", "mode: Invalid"].join("\n"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("uses trimmed message when issues are missing", () => {
|
||||||
|
const error = {
|
||||||
|
name: "ConfigInvalidError",
|
||||||
|
data: {
|
||||||
|
path: "config",
|
||||||
|
message: " Bad value ",
|
||||||
|
},
|
||||||
|
} satisfies ConfigInvalidError
|
||||||
|
|
||||||
|
const result = parseReabaleConfigInvalidError(error)
|
||||||
|
|
||||||
|
expect(result).toBe(["Invalid configuration", "Bad value"].join("\n"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("formatServerError", () => {
|
||||||
|
test("formats config invalid errors", () => {
|
||||||
|
const error = {
|
||||||
|
name: "ConfigInvalidError",
|
||||||
|
data: {
|
||||||
|
message: "Missing host",
|
||||||
|
},
|
||||||
|
} satisfies ConfigInvalidError
|
||||||
|
|
||||||
|
const result = formatServerError(error)
|
||||||
|
|
||||||
|
expect(result).toBe(["Invalid configuration", "Missing host"].join("\n"))
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns error messages", () => {
|
||||||
|
expect(formatServerError(new Error("Request failed with status 503"))).toBe("Request failed with status 503")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns provided string errors", () => {
|
||||||
|
expect(formatServerError("Failed to connect to server")).toBe("Failed to connect to server")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back to unknown", () => {
|
||||||
|
expect(formatServerError(0)).toBe("Unknown error")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back for unknown error objects and names", () => {
|
||||||
|
expect(formatServerError({ name: "ServerTimeoutError", data: { seconds: 30 } })).toBe("Unknown error")
|
||||||
|
})
|
||||||
|
})
|
||||||
32
packages/app/src/utils/server-errors.ts
Normal file
32
packages/app/src/utils/server-errors.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
export type ConfigInvalidError = {
|
||||||
|
name: "ConfigInvalidError"
|
||||||
|
data: {
|
||||||
|
path?: string
|
||||||
|
message?: string
|
||||||
|
issues?: Array<{ message: string; path: string[] }>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatServerError(error: unknown) {
|
||||||
|
if (isConfigInvalidErrorLike(error)) return parseReabaleConfigInvalidError(error)
|
||||||
|
if (error instanceof Error && error.message) return error.message
|
||||||
|
if (typeof error === "string" && error) return error
|
||||||
|
return "Unknown error"
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConfigInvalidErrorLike(error: unknown): error is ConfigInvalidError {
|
||||||
|
if (typeof error !== "object" || error === null) return false
|
||||||
|
const o = error as Record<string, unknown>
|
||||||
|
return o.name === "ConfigInvalidError" && typeof o.data === "object" && o.data !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseReabaleConfigInvalidError(errorInput: ConfigInvalidError) {
|
||||||
|
const head = "Invalid configuration"
|
||||||
|
const file = errorInput.data.path && errorInput.data.path !== "config" ? errorInput.data.path : ""
|
||||||
|
const detail = errorInput.data.message?.trim() ?? ""
|
||||||
|
const issues = (errorInput.data.issues ?? []).map((issue) => {
|
||||||
|
return `${issue.path.join(".")}: ${issue.message}`
|
||||||
|
})
|
||||||
|
if (issues.length) return [head, file, "", ...issues].filter(Boolean).join("\n")
|
||||||
|
return [head, file, detail].filter(Boolean).join("\n")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user