UNPKG

@contentstack/cli-utilities

Version:

Utilities for contentstack projects

299 lines (298 loc) 12.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CLIErrorHandler = void 0; const helpers_1 = require("../helpers"); const errorTypes_1 = require("../constants/errorTypes"); const helpers_2 = require("../helpers"); /** * 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(); * * 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() { } /** * 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. * @param errMessage - Optional custom error message to override the default error message. * * @returns A `ClassifiedError` object containing essential error details in a clear, * concise format optimized for debugging. */ classifyError(error, context, errMessage) { try { const normalized = this.normalizeToError(error); const type = this.determineErrorType(normalized); const hidden = this.containsSensitiveInfo(normalized); const result = { type, message: errMessage || this.extractClearMessage(normalized), error: this.extractErrorPayload(normalized), meta: this.extractMeta(context, type), hidden, }; return result; } catch (e) { return { type: errorTypes_1.ERROR_TYPES.NORMALIZATION, message: 'Failed to process error', error: { originalError: String(e), errorType: typeof error, }, meta: this.extractMeta(context, errorTypes_1.ERROR_TYPES.NORMALIZATION), hidden: false, }; } } /** * Extracts a clear, concise error message from various error types. */ extractClearMessage(error) { var _a, _b; // Use existing formatError function for other cases try { const formattedMessage = (0, helpers_1.formatError)(error); return formattedMessage || 'An error occurred. Please try again.'; } catch (_c) { // Fallback to basic error message extraction if formatError fails if (typeof ((_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.errorMessage) === 'string') { return error.response.data.errorMessage; } if (typeof (error === null || error === void 0 ? void 0 : error.errorMessage) === 'string') { return error.errorMessage; } } } /** * Normalizes various error types into a standard Error object. * * @param error - The error to normalize * @returns A normalized Error object */ 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 errorObj = error; const message = errorObj.message || errorObj.error || errorObj.statusText || 'Unknown error'; const normalizedError = new Error(message); // Only copy essential properties const essentialProps = [ 'code', 'status', 'statusText', 'response', 'request', 'message', 'errorMessage', 'error_message', 'error', 'errors', ]; essentialProps.forEach((prop) => { if (errorObj[prop] !== undefined) { normalizedError[prop] = errorObj[prop]; } }); return normalizedError; } catch (_a) { return new Error(JSON.stringify(error)); } } return new Error(String(error)); } /** * Determines the type of error based on its characteristics. */ determineErrorType(error) { const { status, code, name, response } = error; const actualStatus = status || (response === null || response === void 0 ? void 0 : response.status); // Network and timeout errors if (['ECONNREFUSED', 'ENOTFOUND', 'ETIMEDOUT', 'ENETUNREACH'].includes(code)) { return errorTypes_1.ERROR_TYPES.NETWORK; } // HTTP status-based classification if (actualStatus) { if (actualStatus >= 100 && actualStatus < 200) return errorTypes_1.ERROR_TYPES.INFORMATIONAL; if (actualStatus >= 300 && actualStatus < 400) return errorTypes_1.ERROR_TYPES.REDIRECTION; if (actualStatus >= 400 && actualStatus < 500) return errorTypes_1.ERROR_TYPES.API_ERROR; if (actualStatus >= 500) return errorTypes_1.ERROR_TYPES.SERVER_ERROR; } // Specific error types if (name === 'DatabaseError') return errorTypes_1.ERROR_TYPES.DATABASE; if (error.isAxiosError) return errorTypes_1.ERROR_TYPES.NETWORK; return errorTypes_1.ERROR_TYPES.APPLICATION; } /** * Extracts only essential error payload information for clear debugging. */ extractErrorPayload(error) { var _a, _b, _c, _d; const { name, message, code, status, response, request, config, statusText } = error; const payload = { name, message: this.extractClearMessage(error), }; // Add error identifiers if (code) payload.code = code; if (status || (response === null || response === void 0 ? void 0 : response.status)) payload.status = status || (response === null || response === void 0 ? void 0 : response.status); // Add detailed field-level errors if available if (((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.errors) && typeof response.data.errors === 'object') { payload.errors = response.data.errors; } else if ((error === null || error === void 0 ? void 0 : error.errors) && typeof error.errors === 'object') { payload.errors = error.errors; } // Add error code if available if ((_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.error_code) { payload.errorCode = response.data.error_code; } else if (error === null || error === void 0 ? void 0 : error.error_code) { payload.errorCode = error.error_code; } // Add request context with sensitive data redaction if (request || config) { const requestData = { method: (request === null || request === void 0 ? void 0 : request.method) || (config === null || config === void 0 ? void 0 : config.method), url: (request === null || request === void 0 ? void 0 : request.url) || (config === null || config === void 0 ? void 0 : config.url), headers: (request === null || request === void 0 ? void 0 : request.headers) || (config === null || config === void 0 ? void 0 : config.headers), data: (request === null || request === void 0 ? void 0 : request.data) || (config === null || config === void 0 ? void 0 : config.data), timeout: config === null || config === void 0 ? void 0 : config.timeout, baseURL: config === null || config === void 0 ? void 0 : config.baseURL, params: config === null || config === void 0 ? void 0 : config.params, }; // Use existing redactObject to mask sensitive data payload.request = (0, helpers_2.redactObject)(requestData); } // Add response context with sensitive data redaction if (response) { const responseData = { status, statusText, headers: response.headers, data: response.data, }; // Use existing redactObject to mask sensitive data payload.response = (0, helpers_2.redactObject)(responseData); } // Extract user-friendly error message for API errors if ((_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.errorMessage) { payload.userFriendlyMessage = response.data.errorMessage; } // Add stack trace only for non-API errors to avoid clutter if (![errorTypes_1.ERROR_TYPES.API_ERROR, errorTypes_1.ERROR_TYPES.SERVER_ERROR].includes(this.determineErrorType(error))) { payload.stack = (_d = error.stack) === null || _d === void 0 ? void 0 : _d.split('\n').slice(0, 5).join('\n'); } return payload; } /** * Extracts metadata from the error context and adds additional information. * * @param context - The error context to extract metadata from * @param errorType - Optional error type to include in metadata * @returns An object containing relevant metadata for debugging */ extractMeta(context, errorType) { if (!context) return {}; const baseMeta = {}; if (context.operation) baseMeta.operation = context.operation; if (context.component) baseMeta.component = context.component; if (context.userId) baseMeta.userId = context.userId; if (context.sessionId) baseMeta.sessionId = context.sessionId; if (context.orgId) baseMeta.orgId = context.orgId; if (errorType) baseMeta.errorType = errorType; if (context.email) baseMeta.email = context.email; return baseMeta; } /** * Checks if error contains sensitive information. * * @param error - Error to check * @returns True if sensitive info is found */ containsSensitiveInfo(error) { try { const content = `${error.message} ${error.stack || ''}`.toLowerCase(); const sensitiveTerms = [ 'password', 'token', 'secret', 'credentials', 'api_key', 'api-key', 'authorization', 'sessionid', 'authtoken', 'x-api-key', 'tfa_token', 'otp', 'security_code', 'bearer', 'cookie', ]; return sensitiveTerms.some((term) => content.includes(term)); } catch (_a) { return false; } } } exports.default = CLIErrorHandler; exports.CLIErrorHandler = CLIErrorHandler;