← vibesdk
/**
* Error Handling Utilities
*/
import { createLogger } from '../logger';
import { SecurityError, UsageLimitExceededError } from 'shared/types/errors';
import { errorResponse } from '../api/responses';
const logger = createLogger('ErrorHandling');
/**
* Standard error types for the application
*/
export enum AppErrorType {
VALIDATION_ERROR = 'VALIDATION_ERROR',
AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR',
AUTHORIZATION_ERROR = 'AUTHORIZATION_ERROR',
NOT_FOUND_ERROR = 'NOT_FOUND_ERROR',
CONFLICT_ERROR = 'CONFLICT_ERROR',
RATE_LIMIT_ERROR = 'RATE_LIMIT_ERROR',
EXTERNAL_SERVICE_ERROR = 'EXTERNAL_SERVICE_ERROR',
INTERNAL_ERROR = 'INTERNAL_ERROR'
}
/**
* Application error class
*/
export class AppError extends Error {
constructor(
public type: AppErrorType,
message: string,
public statusCode: number = 500,
public context?: Record<string, any>
) {
super(message);
this.name = 'AppError';
}
}
/**
* Error handling utilities
*/
export class ErrorHandler {
/**
* Handle and log error with context
*/
static handleError(
error: unknown,
operation: string,
context?: Record<string, any>
): AppError {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.error(`Error during ${operation}`, {
error: errorMessage,
stack: error instanceof Error ? error.stack : undefined,
...context
});
// Convert SecurityError to AppError
if (error instanceof SecurityError) {
return new AppError(
AppErrorType.AUTHENTICATION_ERROR,
error.message,
error.statusCode,
context
);
}
// Handle UsageLimitExceededError
if (error instanceof UsageLimitExceededError) {
return new AppError(
AppErrorType.RATE_LIMIT_ERROR,
error.message,
429,
{
...context,
exceededLimits: error.exceededLimits,
hasUserToken: error.hasUserToken,
errorType: 'USAGE_LIMIT_EXCEEDED'
}
);
}
// Convert AppError
if (error instanceof AppError) {
return error;
}
// Default to internal error
return new AppError(
AppErrorType.INTERNAL_ERROR,
`Failed to ${operation}`,
500,
context
);
}
/**
* Convert AppError to HTTP Response
*/
static toResponse(error: AppError): Response {
// For usage limit errors, include context in response
if (error.context?.errorType === 'USAGE_LIMIT_EXCEEDED') {
const errorObj: Error & { errorType?: string; exceededLimits?: unknown; hasUserToken?: boolean } =
Object.assign(new Error(error.message), {
errorType: error.context.errorType as string,
exceededLimits: error.context.exceededLimits,
hasUserToken: error.context.hasUserToken as boolean | undefined,
});
return errorResponse(errorObj, error.statusCode);
}
return errorResponse(error.message, error.statusCode);
}
/**
* Handle async operation with error catching
*/
static async safeExecute<T>(
operation: () => Promise<T>,
operationName: string,
context?: Record<string, any>
): Promise<{ success: true; data: T } | { success: false; error: AppError }> {
try {
const data = await operation();
return { success: true, data };
} catch (error) {
const appError = ErrorHandler.handleError(error, operationName, context);
return { success: false, error: appError };
}
}
/**
* Wrap async function with error handling
*/
static wrapAsync<TArgs extends any[], TReturn>(
fn: (...args: TArgs) => Promise<TReturn>,
operationName: string,
defaultReturn?: TReturn
) {
return async (...args: TArgs): Promise<TReturn> => {
try {
return await fn(...args);
} catch (error) {
const appError = ErrorHandler.handleError(error, operationName);
if (defaultReturn !== undefined) {
return defaultReturn;
}
throw appError;
}
};
}
}
/**
* Error factory functions for common scenarios
*/
export class ErrorFactory {
static validationError(message: string, context?: Record<string, any>): AppError {
return new AppError(AppErrorType.VALIDATION_ERROR, message, 400, context);
}
static authenticationError(message: string = 'Authentication required', context?: Record<string, any>): AppError {
return new AppError(AppErrorType.AUTHENTICATION_ERROR, message, 401, context);
}
static authorizationError(message: string = 'Insufficient permissions', context?: Record<string, any>): AppError {
return new AppError(AppErrorType.AUTHORIZATION_ERROR, message, 403, context);
}
static notFoundError(resource: string, context?: Record<string, any>): AppError {
return new AppError(AppErrorType.NOT_FOUND_ERROR, `${resource} not found`, 404, context);
}
static conflictError(message: string, context?: Record<string, any>): AppError {
return new AppError(AppErrorType.CONFLICT_ERROR, message, 409, context);
}
static rateLimitError(message: string = 'Rate limit exceeded', context?: Record<string, any>): AppError {
return new AppError(AppErrorType.RATE_LIMIT_ERROR, message, 429, context);
}
static externalServiceError(service: string, context?: Record<string, any>): AppError {
return new AppError(
AppErrorType.EXTERNAL_SERVICE_ERROR,
`External service ${service} unavailable`,
502,
context
);
}
static internalError(message: string = 'Internal server error', context?: Record<string, any>): AppError {
return new AppError(AppErrorType.INTERNAL_ERROR, message, 500, context);
}
}
/**
* Controller error handling mixin
*/
export class ControllerErrorHandler {
/**
* Handle controller operation with standardized error response
*/
static async handleControllerOperation<T>(
operation: () => Promise<T>,
operationName: string,
context?: Record<string, any>
): Promise<T | Response> {
try {
return await operation();
} catch (error) {
const appError = ErrorHandler.handleError(error, operationName, context);
return ErrorHandler.toResponse(appError);
}
}
/**
* Validate required parameters
*/
static validateRequiredParams(
params: Record<string, any>,
requiredFields: string[]
): void {
for (const field of requiredFields) {
if (!params[field]) {
throw ErrorFactory.validationError(`${field} is required`, { field });
}
}
}
/**
* Handle authentication requirement
*/
static requireAuthentication(user: any): void {
if (!user) {
throw ErrorFactory.authenticationError();
}
}
/**
* Handle resource ownership verification
*/
static requireResourceOwnership(resource: any, userId: string, resourceName: string): void {
if (!resource) {
throw ErrorFactory.notFoundError(resourceName);
}
if (resource.userId !== userId) {
throw ErrorFactory.authorizationError(`Access denied to ${resourceName}`);
}
}
/**
* Handle JSON parsing with proper error
*/
static async parseJsonBody<T>(request: Request): Promise<T> {
try {
return await request.json() as T;
} catch (error) {
throw ErrorFactory.validationError('Invalid JSON in request body');
}
}
/**
* Handle external service errors
*/
static handleExternalServiceError(
serviceName: string,
error: unknown,
context?: Record<string, any>
): never {
logger.error(`External service error: ${serviceName}`, {
error: error instanceof Error ? error.message : 'Unknown error',
...context
});
throw ErrorFactory.externalServiceError(serviceName, context);
}
}