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
JavaScript
/**
* @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),
};
;