koatty_exception
Version:
Exception handler for koatty.
1,082 lines (1,078 loc) • 29.9 kB
JavaScript
import { IOCContainer } from 'koatty_container';
import * as Helper3 from 'koatty_lib';
import { Helper } from 'koatty_lib';
import { DefaultLogger } from 'koatty_logger';
import { SpanStatusCode } from '@opentelemetry/api';
import { ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from '@opentelemetry/semantic-conventions';
/*!
* @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 (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 = IOCContainer.getIdentifier(target);
if (!(target.prototype instanceof Exception)) {
throw new Error(`class ${identifier} does not inherit from class 'Exception'`);
}
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 && 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 && 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) {
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);
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: SpanStatusCode.ERROR,
message: this.message
});
this.span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, ctx.status);
this.span.setAttribute(ATTR_HTTP_REQUEST_METHOD, ctx.method);
this.span.setAttribute(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)) {
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.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.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(` 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");
export { Catch, CommonErrorCode, Exception, ExceptionHandler, GrpcStatusCode, GrpcStatusCodeMap, HttpStatusCode, HttpStatusCodeMap, Output, StatusCodeConvert, getExceptionConfig, isException, isNetworkError, prevent, setExceptionConfig, toSafeError };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map