UNPKG

@contentstack/cli-utilities

Version:

Utilities for contentstack projects

218 lines (217 loc) 9.09 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CLIErrorHandler = void 0; const helpers_1 = require("../helpers"); const errorTypes_1 = require("../constants/errorTypes"); /** * Handles errors in a CLI application by classifying, normalizing, and extracting * relevant information for debugging and logging purposes. * * This class provides methods to: * - Normalize unknown error types into standard `Error` objects. * - Classify errors into predefined categories such as API errors, network errors, * server errors, and more. * - Extract detailed error payloads for logging, including HTTP request and response * details when applicable. * - Identify sensitive information in error messages to prevent accidental exposure. * - Generate debug payloads for enhanced troubleshooting when debugging is enabled. * * @remarks * This class is designed to handle a wide range of error types, including generic * JavaScript errors, API errors, and custom error objects. It also supports * optional debugging and context metadata for enhanced error reporting. * * @example * ```typescript * const errorHandler = new CLIErrorHandler(true); * * try { * // Some operation that may throw an error * } catch (error) { * const classifiedError = errorHandler.classifyError(error, { * operation: 'fetchData', * component: 'DataService', * }); * console.error(classifiedError); * } * ``` * * @public */ class CLIErrorHandler { constructor(isDebug = false) { this.isDebug = isDebug; } /** * Classifies an error into a structured format for better handling and debugging. * * @param error - The error object to classify. Can be of any type. * @param context - Optional additional context about the error, typically used to provide * more information about where or why the error occurred. * * @returns A `ClassifiedError` object containing details about the error, including its type, * message, payload, context, metadata, and whether it contains sensitive information. * If the error is an API error or debugging is enabled, additional debug information * is included. * * @throws This method handles its own errors and will return a `ClassifiedError` with type * `ERROR_TYPES.NORMALIZATION` if it fails to normalize or classify the input error. */ classifyError(error, context) { try { const normalized = this.normalizeToError(error); const isApi = this.isApiError(normalized); const type = this.determineErrorType(normalized); const hidden = this.containsSensitiveInfo(normalized); const result = { type, message: normalized.message || 'Unhandled error', error: this.extractErrorPayload(normalized), context: context ? JSON.stringify(context) : undefined, meta: this.extractMeta(context), hidden, }; if (isApi || this.isDebug) { result.debug = this.extractDebugPayload(normalized, context); } return result; } catch (e) { return { type: errorTypes_1.ERROR_TYPES.NORMALIZATION, message: 'Failed to normalize or classify error', error: { message: String(e) }, context: context ? JSON.stringify(context) : undefined, meta: this.extractMeta(context), hidden: false, }; } } normalizeToError(error) { if (!error) return new Error('Unknown error occurred'); if (error instanceof Error) return error; if (typeof error === 'string') return new Error(error); if (typeof error === 'object') { try { const msg = error.message; const err = new Error(msg || 'Unknown error'); Object.assign(err, error); return err; } catch (_a) { return new Error(JSON.stringify(error)); } } return new Error(String(error)); } isApiError(error) { return (error.isAxiosError || typeof error.status === 'number' || typeof error.statusText === 'string' || error.request !== undefined); } determineErrorType(error) { var _a; const status = error.status || ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status); //Ignore 4XX errors if (status >= 400 && status < 500) { return errorTypes_1.ERROR_TYPES.API_ERROR; } //Server-side HTTP errors if (status >= 500) { return errorTypes_1.ERROR_TYPES.SERVER_ERROR; } //Network-related error if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND' || error.code === 'ETIMEDOUT') { return errorTypes_1.ERROR_TYPES.NETWORK; } //Database error if (error.name === 'DatabaseError') { return errorTypes_1.ERROR_TYPES.DATABASE; } //Axios errors without 4XX if (error.isAxiosError) { return errorTypes_1.ERROR_TYPES.NETWORK; } //Default return errorTypes_1.ERROR_TYPES.APPLICATION; } extractErrorPayload(error) { var _a, _b, _c, _d, _e, _f; const code = error.code || error.errorCode; const status = error.status || ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status); const statusText = error.statusText || ((_b = error.response) === null || _b === void 0 ? void 0 : _b.statusText); const method = ((_c = error.request) === null || _c === void 0 ? void 0 : _c.method) || ((_d = error.config) === null || _d === void 0 ? void 0 : _d.method) || 'UNKNOWN'; const url = ((_e = error.request) === null || _e === void 0 ? void 0 : _e.url) || ((_f = error.config) === null || _f === void 0 ? void 0 : _f.url); const endpoint = url ? new URL(url, 'http://dummy').pathname : 'UNKNOWN'; const payload = { name: error.name, message: (0, helpers_1.formatError)(error), code, status, statusText, method, endpoint, }; if (this.isDebug) { payload.stack = error.stack; } return payload; } extractDebugPayload(error, context) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; const method = ((_a = error.request) === null || _a === void 0 ? void 0 : _a.method) || ((_b = error.config) === null || _b === void 0 ? void 0 : _b.method); const url = ((_c = error.request) === null || _c === void 0 ? void 0 : _c.url) || ((_d = error.config) === null || _d === void 0 ? void 0 : _d.url); const status = error.status || ((_e = error.response) === null || _e === void 0 ? void 0 : _e.status); const statusText = error.statusText || ((_f = error.response) === null || _f === void 0 ? void 0 : _f.statusText); const data = error.data || ((_g = error.response) === null || _g === void 0 ? void 0 : _g.data) || error.errors || error.error; return { command: context === null || context === void 0 ? void 0 : context.operation, module: context === null || context === void 0 ? void 0 : context.component, request: { method, url, headers: (_h = error.request) === null || _h === void 0 ? void 0 : _h.headers, data: (_j = error.request) === null || _j === void 0 ? void 0 : _j.data, }, response: { status, statusText, data, }, }; } extractMeta(context) { return { email: context === null || context === void 0 ? void 0 : context.email, sessionId: context === null || context === void 0 ? void 0 : context.sessionId, userId: context === null || context === void 0 ? void 0 : context.userId, apiKey: context === null || context === void 0 ? void 0 : context.apiKey, orgId: context === null || context === void 0 ? void 0 : context.orgId, }; } containsSensitiveInfo(error) { try { const content = `${error.message} ${error.stack || ''}`.toLowerCase(); return [ 'password', 'token', 'secret', 'credentials', 'api_key', 'api-key', 'authorization', 'sessionid', 'email', ].some((term) => content.includes(term)); } catch (_a) { return false; } } } exports.default = CLIErrorHandler; exports.CLIErrorHandler = CLIErrorHandler;