UNPKG

hikma-engine

Version:

Code Knowledge Graph Indexer - A sophisticated TypeScript-based indexer that transforms Git repositories into multi-dimensional knowledge stores for AI agents

351 lines (350 loc) 15.6 kB
"use strict"; /** * @file Structured error classes for comprehensive API error handling. * Provides detailed error information with proper HTTP status codes and helpful messages. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.APIErrors = exports.ServiceUnavailableError = exports.InternalServerError = exports.ConfigurationError = exports.CacheError = exports.DatabaseError = exports.SearchServiceError = exports.RateLimitError = exports.NotFoundError = exports.AuthorizationError = exports.AuthenticationError = exports.ValidationError = exports.BaseAPIError = void 0; exports.createAPIError = createAPIError; exports.isOperationalError = isOperationalError; const logger_1 = require("../../utils/logger"); const logger = (0, logger_1.getLogger)('APIErrors'); /** * Base API error class with structured information. */ class BaseAPIError extends Error { constructor(message, details, context, requestId) { super(message); this.name = this.constructor.name; this.timestamp = new Date().toISOString(); this.details = details; this.context = context; this.requestId = requestId; // Ensure proper prototype chain for instanceof checks Object.setPrototypeOf(this, new.target.prototype); // Capture stack trace if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } /** * Converts error to JSON format for API responses. */ toJSON() { return { success: false, error: { code: this.errorCode, message: this.message, details: this.details, ...(process.env.NODE_ENV === 'development' && { stack: this.stack }), }, meta: { timestamp: this.timestamp, requestId: this.requestId, context: this.context, }, }; } /** * Logs the error with appropriate level and context. */ log() { const logData = { errorCode: this.errorCode, message: this.message, statusCode: this.statusCode, details: this.details, context: this.context, requestId: this.requestId, stack: this.stack, }; if (this.statusCode >= 500) { logger.error(`${this.errorCode}: ${this.message}`, logData); } else if (this.statusCode >= 400) { logger.warn(`${this.errorCode}: ${this.message}`, logData); } else { logger.info(`${this.errorCode}: ${this.message}`, logData); } } } exports.BaseAPIError = BaseAPIError; /** * Validation errors (400 Bad Request). */ class ValidationError extends BaseAPIError { constructor(message, field, value, constraint, requestId) { super(message, { field, value, constraint }, { errorType: 'validation' }, requestId); this.statusCode = 400; this.errorCode = 'VALIDATION_ERROR'; this.isOperational = true; } static required(field, requestId) { return new ValidationError(`Field '${field}' is required`, field, undefined, 'required', requestId); } static invalid(field, value, constraint, requestId) { return new ValidationError(`Field '${field}' has invalid value: ${constraint}`, field, value, constraint, requestId); } static format(field, expectedFormat, requestId) { return new ValidationError(`Field '${field}' must be in ${expectedFormat} format`, field, undefined, expectedFormat, requestId); } } exports.ValidationError = ValidationError; /** * Authentication errors (401 Unauthorized). */ class AuthenticationError extends BaseAPIError { constructor(message = 'Authentication required', requestId) { super(message, undefined, { errorType: 'authentication' }, requestId); this.statusCode = 401; this.errorCode = 'AUTHENTICATION_ERROR'; this.isOperational = true; } static invalidToken(requestId) { return new AuthenticationError('Invalid or expired authentication token', requestId); } static missingToken(requestId) { return new AuthenticationError('Authentication token is required', requestId); } } exports.AuthenticationError = AuthenticationError; /** * Authorization errors (403 Forbidden). */ class AuthorizationError extends BaseAPIError { constructor(message = 'Insufficient permissions', requiredPermission, requestId) { super(message, { requiredPermission }, { errorType: 'authorization' }, requestId); this.statusCode = 403; this.errorCode = 'AUTHORIZATION_ERROR'; this.isOperational = true; } static insufficientPermissions(permission, requestId) { return new AuthorizationError(`Insufficient permissions. Required: ${permission}`, permission, requestId); } } exports.AuthorizationError = AuthorizationError; /** * Resource not found errors (404 Not Found). */ class NotFoundError extends BaseAPIError { constructor(resource, identifier, requestId) { const message = identifier ? `${resource} with identifier '${identifier}' not found` : `${resource} not found`; super(message, { resource, identifier }, { errorType: 'not_found' }, requestId); this.statusCode = 404; this.errorCode = 'RESOURCE_NOT_FOUND'; this.isOperational = true; } static route(method, path, requestId) { return new NotFoundError(`Route ${method} ${path}`, undefined, requestId); } static endpoint(endpoint, requestId) { return new NotFoundError(`Endpoint '${endpoint}'`, undefined, requestId); } } exports.NotFoundError = NotFoundError; /** * Rate limiting errors (429 Too Many Requests). */ class RateLimitError extends BaseAPIError { constructor(limit, windowMs, retryAfter, requestId) { super(`Rate limit exceeded. Maximum ${limit} requests per ${windowMs / 1000} seconds`, { limit, windowMs, retryAfter }, { errorType: 'rate_limit' }, requestId); this.statusCode = 429; this.errorCode = 'RATE_LIMIT_EXCEEDED'; this.isOperational = true; } } exports.RateLimitError = RateLimitError; /** * Search service errors (500 Internal Server Error). */ class SearchServiceError extends BaseAPIError { constructor(message, searchType, query, originalError, requestId) { super(message, { searchType, query, originalError: originalError?.message }, { errorType: 'search_service', originalStack: originalError?.stack }, requestId); this.statusCode = 500; this.errorCode = 'SEARCH_SERVICE_ERROR'; this.isOperational = true; } static semanticSearchFailed(query, error, requestId) { return new SearchServiceError('Semantic search operation failed', 'semantic', query, error, requestId); } static structuralSearchFailed(query, error, requestId) { return new SearchServiceError('Structural search operation failed', 'structural', query, error, requestId); } static gitSearchFailed(query, error, requestId) { return new SearchServiceError('Git history search operation failed', 'git', query, error, requestId); } static hybridSearchFailed(query, error, requestId) { return new SearchServiceError('Hybrid search operation failed', 'hybrid', query, error, requestId); } static comprehensiveSearchFailed(query, error, requestId) { return new SearchServiceError('Comprehensive search operation failed', 'comprehensive', query, error, requestId); } } exports.SearchServiceError = SearchServiceError; /** * Database errors (500 Internal Server Error). */ class DatabaseError extends BaseAPIError { constructor(message, operation, database, originalError, requestId) { super(message, { operation, database, originalError: originalError?.message }, { errorType: 'database', originalStack: originalError?.stack }, requestId); this.statusCode = 500; this.errorCode = 'DATABASE_ERROR'; this.isOperational = true; } static connectionFailed(database, error, requestId) { return new DatabaseError(`Failed to connect to ${database} database`, 'connect', database, error, requestId); } static queryFailed(operation, error, requestId) { return new DatabaseError(`Database query failed: ${operation}`, operation, 'unknown', error, requestId); } } exports.DatabaseError = DatabaseError; /** * Cache errors (500 Internal Server Error). */ class CacheError extends BaseAPIError { constructor(message, operation, originalError, requestId) { super(message, { operation, originalError: originalError?.message }, { errorType: 'cache', originalStack: originalError?.stack }, requestId); this.statusCode = 500; this.errorCode = 'CACHE_ERROR'; this.isOperational = true; } static operationFailed(operation, error, requestId) { return new CacheError(`Cache operation failed: ${operation}`, operation, error, requestId); } } exports.CacheError = CacheError; /** * Configuration errors (500 Internal Server Error). */ class ConfigurationError extends BaseAPIError { constructor(message, configKey, expectedValue, requestId) { super(message, { configKey, expectedValue }, { errorType: 'configuration' }, requestId); this.statusCode = 500; this.errorCode = 'CONFIGURATION_ERROR'; this.isOperational = false; // These are programming errors } static missing(configKey, requestId) { return new ConfigurationError(`Missing required configuration: ${configKey}`, configKey, undefined, requestId); } static invalid(configKey, expectedValue, requestId) { return new ConfigurationError(`Invalid configuration for ${configKey}. Expected: ${expectedValue}`, configKey, expectedValue, requestId); } } exports.ConfigurationError = ConfigurationError; /** * Generic internal server errors (500 Internal Server Error). */ class InternalServerError extends BaseAPIError { constructor(message = 'An unexpected error occurred', originalError, requestId) { super(message, { originalError: originalError?.message }, { errorType: 'internal', originalStack: originalError?.stack }, requestId); this.statusCode = 500; this.errorCode = 'INTERNAL_SERVER_ERROR'; this.isOperational = false; } static unexpected(error, requestId) { return new InternalServerError('An unexpected error occurred. Please try again later.', error, requestId); } } exports.InternalServerError = InternalServerError; /** * Service unavailable errors (503 Service Unavailable). */ class ServiceUnavailableError extends BaseAPIError { constructor(service, reason, retryAfter, requestId) { super(`${service} service is currently unavailable${reason ? `: ${reason}` : ''}`, { service, reason, retryAfter }, { errorType: 'service_unavailable' }, requestId); this.statusCode = 503; this.errorCode = 'SERVICE_UNAVAILABLE'; this.isOperational = true; } static searchService(reason, requestId) { return new ServiceUnavailableError('Search', reason, 60, requestId); } static database(database, reason, requestId) { return new ServiceUnavailableError(`${database} database`, reason, 30, requestId); } } exports.ServiceUnavailableError = ServiceUnavailableError; /** * Utility function to create appropriate error from unknown error. */ function createAPIError(error, requestId, context) { // If it's already an API error, return it if (error instanceof BaseAPIError) { return error; } // If it's a standard Error if (error instanceof Error) { // Check for specific error patterns if (error.message.includes('ECONNREFUSED') || error.message.includes('connection')) { return new ServiceUnavailableError('Database', error.message, 30, requestId); } if (error.message.includes('timeout')) { return new ServiceUnavailableError('Service', 'Request timeout', 60, requestId); } if (error.message.includes('validation') || error.message.includes('invalid')) { return new ValidationError(error.message, undefined, undefined, undefined, requestId); } // Default to internal server error return new InternalServerError(error.message, error, requestId); } // For non-Error objects const message = typeof error === 'string' ? error : 'An unknown error occurred'; return new InternalServerError(message, undefined, requestId); } /** * Type guard to check if error is operational. */ function isOperationalError(error) { return error instanceof BaseAPIError && error.isOperational; } /** * Error factory for common API errors. */ exports.APIErrors = { // Validation errors validation: { required: (field, requestId) => ValidationError.required(field, requestId), invalid: (field, value, constraint, requestId) => ValidationError.invalid(field, value, constraint, requestId), format: (field, format, requestId) => ValidationError.format(field, format, requestId), }, // Authentication errors auth: { invalidToken: (requestId) => AuthenticationError.invalidToken(requestId), missingToken: (requestId) => AuthenticationError.missingToken(requestId), insufficientPermissions: (permission, requestId) => AuthorizationError.insufficientPermissions(permission, requestId), }, // Not found errors notFound: { route: (method, path, requestId) => NotFoundError.route(method, path, requestId), endpoint: (endpoint, requestId) => NotFoundError.endpoint(endpoint, requestId), resource: (resource, id, requestId) => new NotFoundError(resource, id, requestId), }, // Search errors search: { semanticFailed: (query, error, requestId) => SearchServiceError.semanticSearchFailed(query, error, requestId), structuralFailed: (query, error, requestId) => SearchServiceError.structuralSearchFailed(query, error, requestId), gitFailed: (query, error, requestId) => SearchServiceError.gitSearchFailed(query, error, requestId), hybridFailed: (query, error, requestId) => SearchServiceError.hybridSearchFailed(query, error, requestId), comprehensiveFailed: (query, error, requestId) => SearchServiceError.comprehensiveSearchFailed(query, error, requestId), }, // Service errors service: { unavailable: (service, reason, requestId) => new ServiceUnavailableError(service, reason, 60, requestId), searchUnavailable: (reason, requestId) => ServiceUnavailableError.searchService(reason, requestId), databaseUnavailable: (database, reason, requestId) => ServiceUnavailableError.database(database, reason, requestId), }, // Generic errors internal: (message, error, requestId) => new InternalServerError(message, error, requestId), rateLimit: (limit, windowMs, retryAfter, requestId) => new RateLimitError(limit, windowMs, retryAfter, requestId), // Authentication errors authentication: (message, requestId) => new AuthenticationError(message, requestId), // Authorization errors authorization: (message, requestId) => new AuthorizationError(message, requestId), // Timeout errors timeout: (message, requestId) => new InternalServerError(message, undefined, requestId), };