UNPKG

koatty_exception

Version:
1,118 lines (1,111 loc) 30.9 kB
'use strict'; var koatty_container = require('koatty_container'); var Helper3 = require('koatty_lib'); var koatty_logger = require('koatty_logger'); var api = require('@opentelemetry/api'); var semanticConventions = require('@opentelemetry/semantic-conventions'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var Helper3__namespace = /*#__PURE__*/_interopNamespace(Helper3); /*! * @Author: richen * @Date: 2026-04-24 08:20:25 * @License: BSD (3-Clause) * @Copyright (c) - <richenlin(at)gmail.com> * @HomePage: https://koatty.org/ */ var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/code.ts var HttpStatusCode = /* @__PURE__ */ (function(HttpStatusCode2) { HttpStatusCode2[HttpStatusCode2["CONTINUE"] = 100] = "CONTINUE"; HttpStatusCode2[HttpStatusCode2["SWITCHING_PROTOCOLS"] = 101] = "SWITCHING_PROTOCOLS"; HttpStatusCode2[HttpStatusCode2["PROCESSING"] = 102] = "PROCESSING"; HttpStatusCode2[HttpStatusCode2["OK"] = 200] = "OK"; HttpStatusCode2[HttpStatusCode2["CREATED"] = 201] = "CREATED"; HttpStatusCode2[HttpStatusCode2["ACCEPTED"] = 202] = "ACCEPTED"; HttpStatusCode2[HttpStatusCode2["NON_AUTHORITATIVE_INFORMATION"] = 203] = "NON_AUTHORITATIVE_INFORMATION"; HttpStatusCode2[HttpStatusCode2["NO_CONTENT"] = 204] = "NO_CONTENT"; HttpStatusCode2[HttpStatusCode2["RESET_CONTENT"] = 205] = "RESET_CONTENT"; HttpStatusCode2[HttpStatusCode2["PARTIAL_CONTENT"] = 206] = "PARTIAL_CONTENT"; HttpStatusCode2[HttpStatusCode2["MULTI_STATUS"] = 207] = "MULTI_STATUS"; HttpStatusCode2[HttpStatusCode2["ALREADY_REPORTED"] = 208] = "ALREADY_REPORTED"; HttpStatusCode2[HttpStatusCode2["IM_USED"] = 226] = "IM_USED"; HttpStatusCode2[HttpStatusCode2["MULTIPLE_CHOICES"] = 300] = "MULTIPLE_CHOICES"; HttpStatusCode2[HttpStatusCode2["MOVED_PERMANENTLY"] = 301] = "MOVED_PERMANENTLY"; HttpStatusCode2[HttpStatusCode2["FOUND"] = 302] = "FOUND"; HttpStatusCode2[HttpStatusCode2["SEE_OTHER"] = 303] = "SEE_OTHER"; HttpStatusCode2[HttpStatusCode2["NOT_MODIFIED"] = 304] = "NOT_MODIFIED"; HttpStatusCode2[HttpStatusCode2["USE_PROXY"] = 305] = "USE_PROXY"; HttpStatusCode2[HttpStatusCode2["SWITCH_PROXY"] = 306] = "SWITCH_PROXY"; HttpStatusCode2[HttpStatusCode2["TEMPORARY_REDIRECT"] = 307] = "TEMPORARY_REDIRECT"; HttpStatusCode2[HttpStatusCode2["PERMANENT_REDIRECT"] = 308] = "PERMANENT_REDIRECT"; HttpStatusCode2[HttpStatusCode2["BAD_REQUEST"] = 400] = "BAD_REQUEST"; HttpStatusCode2[HttpStatusCode2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED"; HttpStatusCode2[HttpStatusCode2["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED"; HttpStatusCode2[HttpStatusCode2["FORBIDDEN"] = 403] = "FORBIDDEN"; HttpStatusCode2[HttpStatusCode2["NOT_FOUND"] = 404] = "NOT_FOUND"; HttpStatusCode2[HttpStatusCode2["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED"; HttpStatusCode2[HttpStatusCode2["NOT_ACCEPTABLE"] = 406] = "NOT_ACCEPTABLE"; HttpStatusCode2[HttpStatusCode2["PROXY_AUTHENTICATION_REQUIRED"] = 407] = "PROXY_AUTHENTICATION_REQUIRED"; HttpStatusCode2[HttpStatusCode2["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT"; HttpStatusCode2[HttpStatusCode2["CONFLICT"] = 409] = "CONFLICT"; HttpStatusCode2[HttpStatusCode2["GONE"] = 410] = "GONE"; HttpStatusCode2[HttpStatusCode2["LENGTH_REQUIRED"] = 411] = "LENGTH_REQUIRED"; HttpStatusCode2[HttpStatusCode2["PRECONDITION_FAILED"] = 412] = "PRECONDITION_FAILED"; HttpStatusCode2[HttpStatusCode2["PAYLOAD_TOO_LARGE"] = 413] = "PAYLOAD_TOO_LARGE"; HttpStatusCode2[HttpStatusCode2["URI_TOO_LONG"] = 414] = "URI_TOO_LONG"; HttpStatusCode2[HttpStatusCode2["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE"; HttpStatusCode2[HttpStatusCode2["RANGE_NOT_SATISFIABLE"] = 416] = "RANGE_NOT_SATISFIABLE"; HttpStatusCode2[HttpStatusCode2["EXPECTATION_FAILED"] = 417] = "EXPECTATION_FAILED"; HttpStatusCode2[HttpStatusCode2["I_AM_A_TEAPOT"] = 418] = "I_AM_A_TEAPOT"; HttpStatusCode2[HttpStatusCode2["MISDIRECTED_REQUEST"] = 421] = "MISDIRECTED_REQUEST"; HttpStatusCode2[HttpStatusCode2["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY"; HttpStatusCode2[HttpStatusCode2["LOCKED"] = 423] = "LOCKED"; HttpStatusCode2[HttpStatusCode2["FAILED_DEPENDENCY"] = 424] = "FAILED_DEPENDENCY"; HttpStatusCode2[HttpStatusCode2["UPGRADE_REQUIRED"] = 426] = "UPGRADE_REQUIRED"; HttpStatusCode2[HttpStatusCode2["PRECONDITION_REQUIRED"] = 428] = "PRECONDITION_REQUIRED"; HttpStatusCode2[HttpStatusCode2["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS"; HttpStatusCode2[HttpStatusCode2["REQUEST_HEADER_FIELDS_TOO_LARGE"] = 431] = "REQUEST_HEADER_FIELDS_TOO_LARGE"; HttpStatusCode2[HttpStatusCode2["UNAVAILABLE_FOR_LEGAL_REASONS"] = 451] = "UNAVAILABLE_FOR_LEGAL_REASONS"; HttpStatusCode2[HttpStatusCode2["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR"; HttpStatusCode2[HttpStatusCode2["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED"; HttpStatusCode2[HttpStatusCode2["BAD_GATEWAY"] = 502] = "BAD_GATEWAY"; HttpStatusCode2[HttpStatusCode2["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE"; HttpStatusCode2[HttpStatusCode2["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT"; HttpStatusCode2[HttpStatusCode2["HTTP_VERSION_NOT_SUPPORTED"] = 505] = "HTTP_VERSION_NOT_SUPPORTED"; HttpStatusCode2[HttpStatusCode2["VARIANT_ALSO_NEGOTIATES"] = 506] = "VARIANT_ALSO_NEGOTIATES"; HttpStatusCode2[HttpStatusCode2["INSUFFICIENT_STORAGE"] = 507] = "INSUFFICIENT_STORAGE"; HttpStatusCode2[HttpStatusCode2["LOOP_DETECTED"] = 508] = "LOOP_DETECTED"; HttpStatusCode2[HttpStatusCode2["NOT_EXTENDED"] = 510] = "NOT_EXTENDED"; HttpStatusCode2[HttpStatusCode2["NETWORK_AUTHENTICATION_REQUIRED"] = 511] = "NETWORK_AUTHENTICATION_REQUIRED"; return HttpStatusCode2; })({}); var HttpStatusCodeMap = /* @__PURE__ */ new Map([ [ 100, "CONTINUE" ], [ 101, "SWITCHING_PROTOCOLS" ], [ 102, "PROCESSING" ], [ 200, "OK" ], [ 201, "CREATED" ], [ 202, "ACCEPTED" ], [ 203, "NON_AUTHORITATIVE_INFORMATION" ], [ 204, "NO_CONTENT" ], [ 205, "RESET_CONTENT" ], [ 206, "PARTIAL_CONTENT" ], [ 207, "MULTI_STATUS" ], [ 208, "ALREADY_REPORTED" ], [ 226, "IM_USED" ], [ 300, "MULTIPLE_CHOICES" ], [ 301, "MOVED_PERMANENTLY" ], [ 302, "FOUND" ], [ 303, "SEE_OTHER" ], [ 304, "NOT_MODIFIED" ], [ 305, "USE_PROXY" ], [ 306, "SWITCH_PROXY" ], [ 307, "TEMPORARY_REDIRECT" ], [ 308, "PERMANENT_REDIRECT" ], [ 400, "BAD_REQUEST" ], [ 401, "UNAUTHORIZED" ], [ 402, "PAYMENT_REQUIRED" ], [ 403, "FORBIDDEN" ], [ 404, "NOT_FOUND" ], [ 405, "METHOD_NOT_ALLOWED" ], [ 406, "NOT_ACCEPTABLE" ], [ 407, "PROXY_AUTHENTICATION_REQUIRED" ], [ 408, "REQUEST_TIMEOUT" ], [ 409, "CONFLICT" ], [ 410, "GONE" ], [ 411, "LENGTH_REQUIRED" ], [ 412, "PRECONDITION_FAILED" ], [ 413, "PAYLOAD_TOO_LARGE" ], [ 414, "URI_TOO_LONG" ], [ 415, "UNSUPPORTED_MEDIA_TYPE" ], [ 416, "RANGE_NOT_SATISFIABLE" ], [ 417, "EXPECTATION_FAILED" ], [ 418, "I_AM_A_TEAPOT" ], [ 421, "MISDIRECTED_REQUEST" ], [ 422, "UNPROCESSABLE_ENTITY" ], [ 423, "LOCKED" ], [ 424, "FAILED_DEPENDENCY" ], [ 426, "UPGRADE_REQUIRED" ], [ 428, "PRECONDITION_REQUIRED" ], [ 429, "TOO_MANY_REQUESTS" ], [ 431, "REQUEST_HEADER_FIELDS_TOO_LARGE" ], [ 451, "UNAVAILABLE_FOR_LEGAL_REASONS" ], [ 500, "INTERNAL_SERVER_ERROR" ], [ 501, "NOT_IMPLEMENTED" ], [ 502, "BAD_GATEWAY" ], [ 503, "SERVICE_UNAVAILABLE" ], [ 504, "GATEWAY_TIMEOUT" ], [ 505, "HTTP_VERSION_NOT_SUPPORTED" ], [ 506, "VARIANT_ALSO_NEGOTIATES" ], [ 507, "INSUFFICIENT_STORAGE" ], [ 508, "LOOP_DETECTED" ], [ 510, "NOT_EXTENDED" ], [ 511, "NETWORK_AUTHENTICATION_REQUIRED" ] ]); var GrpcStatusCode = /* @__PURE__ */ (function(GrpcStatusCode2) { GrpcStatusCode2[GrpcStatusCode2["OK"] = 0] = "OK"; GrpcStatusCode2[GrpcStatusCode2["CANCELLED"] = 1] = "CANCELLED"; GrpcStatusCode2[GrpcStatusCode2["UNKNOWN"] = 2] = "UNKNOWN"; GrpcStatusCode2[GrpcStatusCode2["INVALID_ARGUMENT"] = 3] = "INVALID_ARGUMENT"; GrpcStatusCode2[GrpcStatusCode2["DEADLINE_EXCEEDED"] = 4] = "DEADLINE_EXCEEDED"; GrpcStatusCode2[GrpcStatusCode2["NOT_FOUND"] = 5] = "NOT_FOUND"; GrpcStatusCode2[GrpcStatusCode2["ALREADY_EXISTS"] = 6] = "ALREADY_EXISTS"; GrpcStatusCode2[GrpcStatusCode2["PERMISSION_DENIED"] = 7] = "PERMISSION_DENIED"; GrpcStatusCode2[GrpcStatusCode2["RESOURCE_EXHAUSTED"] = 8] = "RESOURCE_EXHAUSTED"; GrpcStatusCode2[GrpcStatusCode2["FAILED_PRECONDITION"] = 9] = "FAILED_PRECONDITION"; GrpcStatusCode2[GrpcStatusCode2["ABORTED"] = 10] = "ABORTED"; GrpcStatusCode2[GrpcStatusCode2["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE"; GrpcStatusCode2[GrpcStatusCode2["UNIMPLEMENTED"] = 12] = "UNIMPLEMENTED"; GrpcStatusCode2[GrpcStatusCode2["INTERNAL"] = 13] = "INTERNAL"; GrpcStatusCode2[GrpcStatusCode2["UNAVAILABLE"] = 14] = "UNAVAILABLE"; GrpcStatusCode2[GrpcStatusCode2["DATA_LOSS"] = 15] = "DATA_LOSS"; GrpcStatusCode2[GrpcStatusCode2["UNAUTHENTICATED"] = 16] = "UNAUTHENTICATED"; return GrpcStatusCode2; })({}); var GrpcStatusCodeMap = /* @__PURE__ */ new Map([ [ 0, "OK" ], [ 1, "CANCELLED" ], [ 2, "UNKNOWN" ], [ 3, "INVALID_ARGUMENT" ], [ 4, "DEADLINE_EXCEEDED" ], [ 5, "NOT_FOUND" ], [ 6, "ALREADY_EXISTS" ], [ 7, "PERMISSION_DENIED" ], [ 8, "RESOURCE_EXHAUSTED" ], [ 9, "FAILED_PRECONDITION" ], [ 10, "ABORTED" ], [ 11, "OUT_OF_RANGE" ], [ 12, "UNIMPLEMENTED" ], [ 13, "INTERNAL" ], [ 14, "UNAVAILABLE" ], [ 15, "DATA_LOSS" ], [ 16, "UNAUTHENTICATED" ] ]); function StatusCodeConvert(code) { switch (code) { case 200: return 0; //GrpcStatusCode.OK; case 304: return 1; //GrpcStatusCode.CANCELLED; case 400: return 3; //GrpcStatusCode.INVALID_ARGUMENT; case 408: return 4; //GrpcStatusCode.DEADLINE_EXCEEDED; case 404: return 5; //GrpcStatusCode.NOT_FOUND; case 409: return 6; //GrpcStatusCode.ALREADY_EXISTS; case 403: return 7; //GrpcStatusCode.PERMISSION_DENIED; case 429: return 8; //GrpcStatusCode.RESOURCE_EXHAUSTED; case 412: return 9; //GrpcStatusCode.FAILED_PRECONDITION; case 421: return 10; //GrpcStatusCode.ABORTED; case 416: return 11; //GrpcStatusCode.OUT_OF_RANGE; case 501: return 12; //GrpcStatusCode.UNIMPLEMENTED; case 500: return 13; //GrpcStatusCode.INTERNAL; case 503: return 14; //GrpcStatusCode.UNAVAILABLE; case 410: return 15; //GrpcStatusCode.DATA_LOSS; case 401: return 16; //GrpcStatusCode.UNAUTHENTICATED; default: return 2; } } __name(StatusCodeConvert, "StatusCodeConvert"); var CommonErrorCode = /* @__PURE__ */ (function(CommonErrorCode2) { CommonErrorCode2[CommonErrorCode2["SUCCESS"] = 0] = "SUCCESS"; CommonErrorCode2[CommonErrorCode2["GENERAL_ERROR"] = 1] = "GENERAL_ERROR"; CommonErrorCode2[CommonErrorCode2["VALIDATION_ERROR"] = 1001] = "VALIDATION_ERROR"; CommonErrorCode2[CommonErrorCode2["AUTHENTICATION_ERROR"] = 1002] = "AUTHENTICATION_ERROR"; CommonErrorCode2[CommonErrorCode2["AUTHORIZATION_ERROR"] = 1003] = "AUTHORIZATION_ERROR"; CommonErrorCode2[CommonErrorCode2["RESOURCE_NOT_FOUND"] = 1004] = "RESOURCE_NOT_FOUND"; CommonErrorCode2[CommonErrorCode2["RESOURCE_CONFLICT"] = 1005] = "RESOURCE_CONFLICT"; CommonErrorCode2[CommonErrorCode2["RATE_LIMIT_EXCEEDED"] = 1006] = "RATE_LIMIT_EXCEEDED"; CommonErrorCode2[CommonErrorCode2["INTERNAL_SERVER_ERROR"] = 1007] = "INTERNAL_SERVER_ERROR"; CommonErrorCode2[CommonErrorCode2["SERVICE_UNAVAILABLE"] = 1008] = "SERVICE_UNAVAILABLE"; CommonErrorCode2[CommonErrorCode2["TIMEOUT_ERROR"] = 1009] = "TIMEOUT_ERROR"; CommonErrorCode2[CommonErrorCode2["NETWORK_ERROR"] = 1010] = "NETWORK_ERROR"; return CommonErrorCode2; })({}); function isJsonResult(obj) { return !!(obj && typeof obj === "object" && "code" in obj && "message" in obj && "data" in obj && typeof obj.code === "number" && typeof obj.message === "string"); } __name(isJsonResult, "isJsonResult"); function isCodeError(obj) { return !!(obj && typeof obj === "object" && ("code" in obj || "message" in obj || "data" in obj)); } __name(isCodeError, "isCodeError"); function safeStringify(value) { if (typeof value === "string") { return value; } if (value === null || value === void 0) { return ""; } if (typeof value === "object") { try { return JSON.stringify(value); } catch { return "[Object object]"; } } return String(value); } __name(safeStringify, "safeStringify"); var Output = class { static { __name(this, "Output"); } /** * Response to normalize json format content for success * * @template T * @param {string | JsonResult<T>} msg - Message or JsonResult object * @param {T} [data] - Response data * @param {number} [code=0] - Success code, default 0 * @returns {JsonResult<T>} Normalized success response */ static ok(msg, data, code = 0) { if (typeof code !== "number" || code < 0) { code = 0; } if (isJsonResult(msg)) { return { code: typeof msg.code === "number" ? msg.code : code, message: safeStringify(msg.message), data: msg.data }; } return { code, message: safeStringify(msg), data }; } /** * Response to normalize json format content for fail * * @template T * @param {Error | CodeError | string | unknown} err - Error object, message, or unknown error * @param {T} [data] - Additional error data * @param {number} [code=1] - Error code, default 1 * @returns {JsonResult<T>} Normalized error response */ static fail(err, data, code = 1) { if (typeof code !== "number" || code < 1) { code = 1; } if (Helper3.Helper.isError(err)) { return { code, message: err.message || "Unknown error", data }; } if (isCodeError(err)) { return { code: typeof err.code === "number" && err.code > 0 ? err.code : code, message: safeStringify(err.message) || "Unknown error", data: err.data || data }; } if (typeof err === "string") { return { code, message: err.trim() || "Unknown error", data }; } return { code, message: safeStringify(err) || "Unknown error occurred", data }; } /** * Create a paginated response * * @template T * @param {T[]} items - Array of items * @param {number} total - Total count * @param {number} page - Current page (1-based) * @param {number} pageSize - Items per page * @param {string} [message='Success'] - Response message * @returns {JsonResult} Paginated response */ static paginate(items, total, page, pageSize, message = "Success") { const safeTotal = Math.max(0, Math.floor(total)); const safePage = Math.max(1, Math.floor(page)); const safePageSize = Math.max(1, Math.floor(pageSize)); const totalPages = Math.ceil(safeTotal / safePageSize); return this.ok(message, { items: Array.isArray(items) ? items : [], pagination: { total: safeTotal, page: safePage, pageSize: safePageSize, totalPages, hasNext: safePage < totalPages, hasPrev: safePage > 1 } }); } /** * Create a response with metadata * * @template T * @param {string} message - Response message * @param {T} data - Response data * @param {Record<string, unknown>} [meta] - Additional metadata * @param {number} [code=0] - Response code * @returns {JsonResult} Response with metadata */ static withMeta(message, data, meta, code = 0) { return this.ok(message, { data, ...meta && Object.keys(meta).length > 0 && { meta } }, code); } }; var globalConfig = { enableStackTrace: process.env.NODE_ENV !== "production", logFormat: "json", maxStackLength: 1e3 }; function setExceptionConfig(config) { globalConfig = { ...globalConfig, ...config }; } __name(setExceptionConfig, "setExceptionConfig"); function getExceptionConfig() { return { ...globalConfig }; } __name(getExceptionConfig, "getExceptionConfig"); function ExceptionHandler() { return (target) => { const identifier = koatty_container.IOCContainer.getIdentifier(target); if (!(target.prototype instanceof Exception)) { throw new Error(`class ${identifier} does not inherit from class 'Exception'`); } koatty_container.IOCContainer.saveClass("COMPONENT", target, "ExceptionHandler"); }; } __name(ExceptionHandler, "ExceptionHandler"); var Exception = class extends Error { static { __name(this, "Exception"); } type = "Exception"; status = 500; code = 1; span; timestamp; context; /** * @description: Creates an instance of Exception. * @param {string} message err message * @param {number} code err code * @param {number} status http status * @param {string} stack err stack * @param {Span} span opentracing span * @return {Exception} */ constructor(message, code, status, stack, span) { super(message); this.timestamp = Date.now(); this.setCode(code); this.setStatus(status); this.setStack(stack); this.span = span; this.name = this.constructor.name; } /** * Set error code with validation * * @param {number} code - Error code * @returns {Exception} This instance for chaining */ setCode(code) { if (code !== void 0 && Helper3.Helper.isNumber(code) && code >= 0) { this.code = Math.floor(code); } return this; } /** * Set HTTP status with validation * * @param {number} status - HTTP status code * @returns {Exception} This instance for chaining */ setStatus(status) { if (status !== void 0 && Helper3.Helper.isNumber(status) && status >= 100 && status < 600) { this.status = Math.floor(status); } return this; } /** * Set error message with validation * * @param {string} message - Error message * @returns {Exception} This instance for chaining */ setMessage(message) { if (message && typeof message === "string") { this.message = message.trim(); } return this; } /** * Set stack trace with validation * * @param {string} stack - Stack trace * @returns {Exception} This instance for chaining */ setStack(stack) { if (stack && typeof stack === "string") { const config = getExceptionConfig(); this.stack = config.maxStackLength && stack.length > config.maxStackLength ? stack.substring(0, config.maxStackLength) + "...[truncated]" : stack; } return this; } /** * Set OpenTelemetry span * * @param {Span} span - OpenTelemetry span * @returns {Exception} This instance for chaining */ setSpan(span) { if (span) { this.span = span; } return this; } /** * Set error context * * @param {Partial<ErrorContext>} context - Error context * @returns {Exception} This instance for chaining */ setContext(context) { this.context = { ...this.context, ...context }; return this; } /** * @description: Default exception handler * @param {IExceptionContext} ctx * @return {Promise<unknown>} */ async handler(ctx) { try { ctx.status = this.status || ctx.status; this.setContext({ requestId: ctx.requestId, path: ctx.originalPath || "/", method: ctx.method, userAgent: ctx.get("User-Agent"), startTime: ctx.startTime, endTime: Date.now(), duration: Date.now() - ctx.startTime }); this.log(ctx); ctx.type = ctx.encoding !== false ? `application/json; charset=${ctx.encoding}` : "application/json"; return this.output(ctx); } catch (error) { koatty_logger.DefaultLogger.Error("Exception handler failed:", error); return this.fallbackOutput(ctx, error); } } /** * @description: 降级输出处理 * @param {IExceptionContext} ctx * @param {unknown} _error * @return {unknown} */ fallbackOutput(ctx, _error) { const isGrpc = ctx.protocol === "grpc"; const isWebSocket = ctx.protocol === "ws" || ctx.protocol === "wss"; const fallbackResponse = { code: 500, message: "Internal Server Error", data: null }; if (isGrpc) { return ctx.rpc.callback({ code: 13, details: JSON.stringify(fallbackResponse) }, null); } const body = JSON.stringify(fallbackResponse); ctx.length = Buffer.byteLength(body); if (isWebSocket) { return ctx.websocket.send(body); } return ctx.res.end(body); } /** * @description: 结构化日志记录 * @param {IExceptionContext} ctx * @return {void} */ log(ctx) { const config = getExceptionConfig(); const logData = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: "ERROR", type: this.type, message: this.message, code: this.code, status: this.status, context: this.context || { requestId: ctx.requestId, path: ctx.originalPath || "/", method: ctx.method, startTime: ctx.startTime, duration: Date.now() - ctx.startTime, endTime: Date.now() }, ...config.enableStackTrace && this.stack && { stack: this.stack } }; const logMessage = config.logFormat === "json" ? JSON.stringify(logData, null, 2) : this.formatTextLog(logData); koatty_logger.DefaultLogger.Error(logMessage); this.updateSpan(ctx); } /** * Format log as text * * @param {LogData} logData - Log data object * @returns {string} Formatted text log */ formatTextLog(logData) { const { timestamp, level, type, message, code, status, context, stack } = logData; let log = `[${timestamp}] ${level}: ${type} - ${message} (code: ${code}, status: ${status})`; if (context) { log += ` Context: ${JSON.stringify(context)}`; } if (stack) { log += ` Stack: ${stack}`; } return log; } /** * Update OpenTelemetry span with error information * * @param {IExceptionContext} ctx - Koatty context */ updateSpan(ctx) { if (this.span) { this.span.setStatus({ code: api.SpanStatusCode.ERROR, message: this.message }); this.span.setAttribute(semanticConventions.ATTR_HTTP_RESPONSE_STATUS_CODE, ctx.status); this.span.setAttribute(semanticConventions.ATTR_HTTP_REQUEST_METHOD, ctx.method); this.span.setAttribute(semanticConventions.ATTR_URL_FULL, ctx.url); this.span.setAttribute("error.code", this.code); this.span.setAttribute("error.type", this.type); } } /** * @description: 输出处理 * @param {IExceptionContext} ctx * @return {unknown} */ output(ctx) { const config = getExceptionConfig(); const isGrpc = ctx.protocol === "grpc"; const isWebSocket = ctx.protocol === "ws" || ctx.protocol === "wss"; const responseBody = config.customErrorFormat ? config.customErrorFormat(this) : this.message || ""; if (isGrpc) { if (this.code < 2) { this.code = StatusCodeConvert(ctx.status); } return ctx.rpc.callback({ code: this.code, details: JSON.stringify(ctx.body || responseBody || this.message || "Internal error") }, null); } if (ctx.res && (ctx.res.writableEnded || ctx.res.finished)) { koatty_logger.DefaultLogger.Warn(`Response already ended for request ${ctx.requestId}, skipping output`); return; } const body = JSON.stringify(Output.fail(responseBody, ctx.body || "", this.code)); ctx.length = Buffer.byteLength(body); if (isWebSocket) { return ctx.websocket.send(body); } return ctx.res.end(body); } /** * Convert exception to plain object * * @returns {object} Plain object representation */ toJSON() { const config = getExceptionConfig(); return { type: this.type, name: this.name, message: this.message, code: this.code, status: this.status, timestamp: this.timestamp, context: this.context, ...config.enableStackTrace && this.stack && { stack: this.stack } }; } }; var PREVENT_NEXT_PROCESS = "PREVENT_NEXT_PROCESS"; function prevent() { return Promise.reject(new Error(PREVENT_NEXT_PROCESS)); } __name(prevent, "prevent"); var isException = /* @__PURE__ */ __name((err) => { if (err instanceof Exception) { return true; } return !!(err && typeof err === "object" && "type" in err && err.type === "Exception" && "message" in err && "code" in err && "status" in err); }, "isException"); function toSafeError(err) { if (Helper3__namespace.isError(err)) { return err; } if (isException(err)) { return err; } if (typeof err === "string") { return new Error(err); } if (typeof err === "number") { return new Error(String(err)); } if (typeof err === "boolean") { return new Error(String(err)); } if (err && typeof err === "object" && "message" in err) { return new Error(String(err.message)); } return new Error("Unknown error occurred"); } __name(toSafeError, "toSafeError"); function isNetworkError(err) { if (!Helper3__namespace.isError(err)) { return false; } const networkErrorCodes = [ "ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT", "ECONNRESET" ]; return networkErrorCodes.includes(err.code); } __name(isNetworkError, "isNetworkError"); // src/catch.ts function isExceptionConstructor(value) { return typeof value === "function" && (value === Exception || value.prototype instanceof Exception); } __name(isExceptionConstructor, "isExceptionConstructor"); function shouldCatch(error, catchTypes) { if (!catchTypes || catchTypes.length === 0) { return true; } return catchTypes.some((ErrorType) => error instanceof ErrorType); } __name(shouldCatch, "shouldCatch"); function parseOptions(args) { if (args.length === 0) { return {}; } const first = args[0]; if (isExceptionConstructor(first)) { return { exception: first }; } if (typeof first === "number") { return { code: first, message: typeof args[1] === "string" ? args[1] : void 0 }; } if (Array.isArray(first)) { const errorTypes = first; const options = args[1] ?? {}; return { ...options, catchTypes: errorTypes }; } if (typeof first === "object" && first !== null) { return first; } return {}; } __name(parseOptions, "parseOptions"); function Catch(...args) { const options = parseOptions(args); return function(target, propertyKey, descriptor) { const originalMethod = descriptor.value; if (typeof originalMethod !== "function") { throw new Error(`@Catch() can only be applied to methods, but "${String(propertyKey)}" is not a function`); } descriptor.value = async function(...methodArgs) { try { const result = originalMethod.apply(this, methodArgs); if (result instanceof Promise) { return await result; } return result; } catch (err) { const safeError = toSafeError(err); if (!shouldCatch(safeError, options.catchTypes)) { throw err; } if (isException(err) && !options.exception && options.code === void 0 && options.message === void 0 && options.status === void 0) { throw err; } const ExceptionClass = options.exception ?? Exception; let errorMessage; if (typeof options.message === "function") { errorMessage = options.message(safeError); } else if (typeof options.message === "string") { errorMessage = options.message; } else { errorMessage = safeError.message; } const useCustomDefaults = options.exception !== void 0 && options.code === void 0 && options.status === void 0; const errorCode = useCustomDefaults ? void 0 : options.code ?? (isException(err) ? err.code : CommonErrorCode.GENERAL_ERROR); const errorStatus = useCustomDefaults ? void 0 : options.status ?? (isException(err) ? err.status : 500); const exception = new ExceptionClass(errorMessage, errorCode, errorStatus); if (options.preserveStack !== false && safeError.stack) { exception.setStack(safeError.stack); } const finalException = options.transform ? options.transform(exception, safeError) : exception; if (options.suppress) { return void 0; } throw finalException; } }; Object.defineProperty(descriptor.value, "name", { value: originalMethod.name, configurable: true }); if (originalMethod.length !== void 0) { Object.defineProperty(descriptor.value, "length", { value: originalMethod.length, configurable: true }); } return descriptor; }; } __name(Catch, "Catch"); exports.Catch = Catch; exports.CommonErrorCode = CommonErrorCode; exports.Exception = Exception; exports.ExceptionHandler = ExceptionHandler; exports.GrpcStatusCode = GrpcStatusCode; exports.GrpcStatusCodeMap = GrpcStatusCodeMap; exports.HttpStatusCode = HttpStatusCode; exports.HttpStatusCodeMap = HttpStatusCodeMap; exports.Output = Output; exports.StatusCodeConvert = StatusCodeConvert; exports.getExceptionConfig = getExceptionConfig; exports.isException = isException; exports.isNetworkError = isNetworkError; exports.prevent = prevent; exports.setExceptionConfig = setExceptionConfig; exports.toSafeError = toSafeError; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map