import type { ErrorBody } from '@dify/contracts/api/openapi/types.gen' import type { ErrorCodeValue, ExitCodeValue } from './codes' import type { ErrorEnvelope, PrintableError } from './format' import { ErrorCode, exitFor } from './codes' export type BaseErrorOptions = { readonly code: ErrorCodeValue readonly message: string readonly hint?: string readonly cause?: unknown } export class BaseError extends Error implements PrintableError { readonly code: ErrorCodeValue readonly hint?: string constructor(opts: BaseErrorOptions) { super(opts.message, opts.cause === undefined ? undefined : { cause: opts.cause }) this.name = 'BaseError' this.code = opts.code this.hint = opts.hint Object.setPrototypeOf(this, new.target.prototype) } exit(): ExitCodeValue { return exitFor(this.code) } override toString(): string { return this.hint ? `${this.code}: ${this.message} (hint: ${this.hint})` : `${this.code}: ${this.message}` } toEnvelope(): ErrorEnvelope { const payload: ErrorEnvelope['error'] = { code: this.code, message: this.message, } if (this.hint !== undefined) payload.hint = this.hint return { error: payload } } withHint(this: T, hint: string): T { const Ctor = this.constructor as new (opts: BaseErrorOptions) => T return new Ctor({ ...this.snapshot(), hint }) } wrap(this: T, cause: unknown): T { const Ctor = this.constructor as new (opts: BaseErrorOptions) => T return new Ctor({ ...this.snapshot(), cause }) } protected snapshot(): BaseErrorOptions { return { code: this.code, message: this.message, hint: this.hint, cause: this.cause, } } } export function newError(code: ErrorCodeValue, message: string): BaseError { return new BaseError({ code, message }) } export function isBaseError(value: unknown): value is BaseError { return value instanceof BaseError } export function isHttpClientError(value: unknown): value is HttpClientError { return value instanceof HttpClientError } export function unknownError(message: string, cause?: unknown): BaseError { return new BaseError({ code: ErrorCode.Unknown, message, cause }) } type HttpClientErrorOptions = BaseErrorOptions & { readonly httpStatus?: number readonly method?: string readonly url?: string readonly rawResponse?: string readonly serverError?: ErrorBody } export class HttpClientError extends BaseError { readonly httpStatus?: number readonly method?: string readonly url?: string readonly rawResponse?: string readonly serverError?: ErrorBody constructor(opts: HttpClientErrorOptions) { super(opts) this.httpStatus = opts.httpStatus this.method = opts.method this.url = opts.url this.rawResponse = opts.rawResponse this.serverError = opts.serverError } override toEnvelope(): ErrorEnvelope { const envelope = super.toEnvelope() if (this.httpStatus !== undefined) envelope.error.http_status = this.httpStatus if (this.method !== undefined) envelope.error.method = this.method if (this.url !== undefined) envelope.error.url = this.url if (this.rawResponse !== undefined) envelope.error.raw_response = this.rawResponse if (this.serverError !== undefined) envelope.error.server = this.serverError return envelope } protected override snapshot(): HttpClientErrorOptions { return { ...super.snapshot(), httpStatus: this.httpStatus, method: this.method, url: this.url, rawResponse: this.rawResponse, serverError: this.serverError, } } public static from(error: BaseError): HttpClientError { return new HttpClientError({ code: error.code, message: error.message, hint: error.hint, cause: error.cause, }) } withHttpStatus(httpStatus: number): HttpClientError { return new HttpClientError({ ...this.snapshot(), httpStatus }) } withRequest(method: string, url: string): HttpClientError { return new HttpClientError({ ...this.snapshot(), method, url }) } withRawResponse(rawResponse: string): HttpClientError { if (!rawResponse) { return this } return new HttpClientError({ ...this.snapshot(), rawResponse }) } withServerError(serverError: ErrorBody): HttpClientError { return new HttpClientError({ ...this.snapshot(), serverError }) } }