mirror of
https://github.com/rustfs/console.git
synced 2026-08-29 03:52:28 +08:00
213 lines
5.8 KiB
TypeScript
213 lines
5.8 KiB
TypeScript
export interface ApiError {
|
|
message: string
|
|
code?: string
|
|
statusCode?: number
|
|
originalError?: Error
|
|
}
|
|
|
|
const GENERIC_ERROR_MESSAGES = new Set(["error", "unknown", "unknownerror"])
|
|
|
|
const normalizeErrorText = (value: unknown): string | null => {
|
|
if (typeof value !== "string") {
|
|
return null
|
|
}
|
|
|
|
const trimmed = value.trim()
|
|
return trimmed ? trimmed : null
|
|
}
|
|
|
|
const isSpecificErrorText = (value: string | null): value is string => {
|
|
return !!value && !GENERIC_ERROR_MESSAGES.has(value.toLowerCase())
|
|
}
|
|
|
|
const getXmlTagText = (xml: string, tagName: string): string | null => {
|
|
const match = xml.match(new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)</${tagName}>`, "i"))
|
|
return normalizeErrorText(match?.[1])
|
|
}
|
|
|
|
export const getXmlErrorCode = (xml: string): string | null => {
|
|
const trimmed = xml.trim()
|
|
if (!trimmed.startsWith("<")) return null
|
|
|
|
if (typeof DOMParser !== "undefined") {
|
|
try {
|
|
const doc = new DOMParser().parseFromString(trimmed, "text/xml")
|
|
if (!doc.querySelector("parsererror")) {
|
|
return normalizeErrorText(doc.querySelector("Code")?.textContent)
|
|
}
|
|
} catch {
|
|
// Fall through to regex-based parsing.
|
|
}
|
|
}
|
|
|
|
return getXmlTagText(trimmed, "Code")
|
|
}
|
|
|
|
export const getXmlErrorMessage = (xml: string): string | null => {
|
|
const trimmed = xml.trim()
|
|
if (!trimmed.startsWith("<")) {
|
|
return null
|
|
}
|
|
|
|
// Use DOMParser for proper XML parsing with automatic entity decoding
|
|
if (typeof DOMParser !== "undefined") {
|
|
try {
|
|
const parser = new DOMParser()
|
|
const doc = parser.parseFromString(trimmed, "text/xml")
|
|
|
|
// Check for parse errors
|
|
const parseError = doc.querySelector("parsererror")
|
|
if (parseError) {
|
|
return null
|
|
}
|
|
|
|
// Try to extract Message first (most specific)
|
|
const message = doc.querySelector("Message")?.textContent?.trim()
|
|
if (message && !GENERIC_ERROR_MESSAGES.has(message.toLowerCase())) {
|
|
return message
|
|
}
|
|
|
|
// Fall back to Code
|
|
const code = doc.querySelector("Code")?.textContent?.trim()
|
|
if (code && !GENERIC_ERROR_MESSAGES.has(code.toLowerCase())) {
|
|
return code
|
|
}
|
|
|
|
// Fall back to Error element text
|
|
const error = doc.querySelector("Error")?.textContent?.trim()
|
|
if (error && !GENERIC_ERROR_MESSAGES.has(error.toLowerCase())) {
|
|
return error
|
|
}
|
|
|
|
return message || code || error || null
|
|
} catch {
|
|
// Fall through to regex-based parsing
|
|
}
|
|
}
|
|
|
|
// Fallback: regex-based parsing (for SSR or if DOMParser fails)
|
|
const message = getXmlTagText(trimmed, "Message")
|
|
if (isSpecificErrorText(message)) {
|
|
return message
|
|
}
|
|
|
|
const code = getXmlTagText(trimmed, "Code")
|
|
if (isSpecificErrorText(code)) {
|
|
return code
|
|
}
|
|
|
|
const error = getXmlTagText(trimmed, "Error")
|
|
if (isSpecificErrorText(error)) {
|
|
return error
|
|
}
|
|
|
|
return message ?? code ?? error
|
|
}
|
|
|
|
export const getServiceErrorMessage = (error: unknown): string | null => {
|
|
if (error instanceof Error) {
|
|
const directMessage = normalizeErrorText(error.message)
|
|
if (isSpecificErrorText(directMessage)) {
|
|
return directMessage
|
|
}
|
|
}
|
|
|
|
if (!error || typeof error !== "object") {
|
|
return null
|
|
}
|
|
|
|
const err = error as {
|
|
Code?: unknown
|
|
Message?: unknown
|
|
name?: unknown
|
|
message?: unknown
|
|
Error?: {
|
|
Code?: unknown
|
|
Message?: unknown
|
|
message?: unknown
|
|
}
|
|
}
|
|
|
|
const codeCandidates = [
|
|
normalizeErrorText(err.Code),
|
|
normalizeErrorText(err.Error?.Code),
|
|
normalizeErrorText(err.name),
|
|
]
|
|
const messageCandidates = [
|
|
normalizeErrorText(err.Message),
|
|
normalizeErrorText(err.Error?.Message),
|
|
normalizeErrorText(err.Error?.message),
|
|
normalizeErrorText(err.message),
|
|
]
|
|
|
|
for (const candidate of codeCandidates) {
|
|
if (isSpecificErrorText(candidate)) {
|
|
return candidate
|
|
}
|
|
}
|
|
|
|
for (const candidate of messageCandidates) {
|
|
if (isSpecificErrorText(candidate)) {
|
|
return candidate
|
|
}
|
|
}
|
|
|
|
return codeCandidates.find(Boolean) ?? messageCandidates.find(Boolean) ?? null
|
|
}
|
|
|
|
export class ConfigLoadError extends Error {
|
|
code: "INVALID_URL" | "STORAGE_ERROR" | "NETWORK_ERROR" | "UNKNOWN_ERROR"
|
|
originalError?: Error
|
|
|
|
constructor(message: string, code: ConfigLoadError["code"] = "UNKNOWN_ERROR", originalError?: Error) {
|
|
super(message)
|
|
this.name = "ConfigLoadError"
|
|
this.code = code
|
|
this.originalError = originalError
|
|
}
|
|
}
|
|
|
|
export const parseApiError = async (response: Response): Promise<string> => {
|
|
try {
|
|
const errorData = await response.clone().json()
|
|
return (errorData as { message?: string }).message || JSON.stringify(errorData) || response.statusText
|
|
} catch {
|
|
try {
|
|
const text = await response.clone().text()
|
|
if (text) {
|
|
if (text.trim().startsWith("<")) {
|
|
return getXmlErrorMessage(text) ?? text
|
|
}
|
|
return text
|
|
}
|
|
} catch {
|
|
// keep statusText
|
|
}
|
|
}
|
|
return response.statusText
|
|
}
|
|
|
|
export const handleConfigError = (error: unknown, context: string): ConfigLoadError => {
|
|
if (error instanceof ConfigLoadError) {
|
|
return error
|
|
}
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes("Invalid URL")) {
|
|
return new ConfigLoadError(`Invalid URL in ${context}: ${error.message}`, "INVALID_URL", error)
|
|
}
|
|
if (error.message.includes("localStorage") || error.message.includes("storage")) {
|
|
return new ConfigLoadError(`Storage error in ${context}: ${error.message}`, "STORAGE_ERROR", error)
|
|
}
|
|
if (error.message.includes("fetch") || error.message.includes("network")) {
|
|
return new ConfigLoadError(`Network error in ${context}: ${error.message}`, "NETWORK_ERROR", error)
|
|
}
|
|
}
|
|
|
|
return new ConfigLoadError(
|
|
`Unknown error in ${context}: ${error}`,
|
|
"UNKNOWN_ERROR",
|
|
error instanceof Error ? error : new Error(String(error)),
|
|
)
|
|
}
|