mirror of
https://github.com/langgenius/dify.git
synced 2026-06-12 19:53:38 +08:00
96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
import type { ErrorBody } from '@dify/contracts/api/openapi/types.gen'
|
|
import type { ErrorCodeValue } from '@/errors/codes'
|
|
import { zErrorBody } from '@dify/contracts/api/openapi/zod.gen'
|
|
import { BaseError, HttpClientError, newError } from '@/errors/base'
|
|
import { ErrorCode } from '@/errors/codes'
|
|
import { redactBearer } from './sanitize'
|
|
|
|
const AUTH_EXPIRED_MESSAGE = 'session expired or revoked'
|
|
const AUTH_LOGIN_HINT = 'run \'difyctl auth login\' to sign in again'
|
|
|
|
// How one HTTP status bucket classifies: CLI code, message fallback when the
|
|
// body is not a canonical ErrorBody, optional CLI hint, raw-body retention.
|
|
type StatusClass = {
|
|
readonly code: ErrorCodeValue
|
|
readonly fallbackMessage: (status: number) => string
|
|
readonly hint?: string
|
|
readonly includeRaw: boolean
|
|
}
|
|
|
|
const AUTH_EXPIRED_CLASS: StatusClass = {
|
|
code: ErrorCode.AuthExpired,
|
|
fallbackMessage: () => AUTH_EXPIRED_MESSAGE,
|
|
hint: AUTH_LOGIN_HINT,
|
|
includeRaw: false,
|
|
}
|
|
|
|
const SERVER_5XX_CLASS: StatusClass = {
|
|
code: ErrorCode.Server5xx,
|
|
fallbackMessage: status => `server error (HTTP ${status})`,
|
|
includeRaw: true,
|
|
}
|
|
|
|
const SERVER_4XX_CLASS: StatusClass = {
|
|
code: ErrorCode.Server4xxOther,
|
|
fallbackMessage: status => `request failed (HTTP ${status})`,
|
|
includeRaw: true,
|
|
}
|
|
|
|
function statusClass(status: number): StatusClass {
|
|
if (status === 401)
|
|
return AUTH_EXPIRED_CLASS
|
|
if (status >= 500)
|
|
return SERVER_5XX_CLASS
|
|
return SERVER_4XX_CLASS
|
|
}
|
|
|
|
function parseServerError(raw: string): ErrorBody | undefined {
|
|
if (raw === '')
|
|
return undefined
|
|
let parsed: unknown
|
|
try {
|
|
parsed = JSON.parse(raw)
|
|
}
|
|
catch {
|
|
return undefined
|
|
}
|
|
const result = zErrorBody.safeParse(parsed)
|
|
return result.success ? result.data : undefined
|
|
}
|
|
|
|
export async function classifyResponse(request: Request, response: Response): Promise<HttpClientError> {
|
|
let raw = ''
|
|
try {
|
|
raw = await response.clone().text()
|
|
}
|
|
catch {
|
|
// ignore read errors; raw stays ''
|
|
}
|
|
|
|
const serverError = parseServerError(raw)
|
|
const status = response.status
|
|
const c = statusClass(status)
|
|
return new HttpClientError({
|
|
code: c.code,
|
|
message: serverError?.message ?? c.fallbackMessage(status),
|
|
hint: c.hint,
|
|
httpStatus: status,
|
|
method: request.method,
|
|
url: redactBearer(response.url || request.url),
|
|
rawResponse: c.includeRaw && raw !== '' ? raw : undefined,
|
|
serverError,
|
|
})
|
|
}
|
|
|
|
export function classifyTransportError(err: unknown): BaseError {
|
|
if (err instanceof BaseError) {
|
|
return err
|
|
}
|
|
if (!(err instanceof Error)) {
|
|
return newError(ErrorCode.Unknown, String(err)).wrap(err)
|
|
}
|
|
const sanitized = redactBearer(err.message)
|
|
// there isn't a practical way to classify network errors reliably
|
|
return newError(ErrorCode.NetworkConnection, sanitized).wrap(err)
|
|
}
|