Skip to main content
Glama
error-handler.ts7.05 kB
import { HttpException, BadRequestException, InternalServerErrorException, ForbiddenException, NotFoundException, UnauthorizedException, } from '@nestjs/common'; import { WsException } from '@nestjs/websockets'; import { logger } from '@snakagent/core'; import { ServerError } from './error.js'; import { JobNotFoundError, JobNotCompletedError, JobFailedError, JobAccessDeniedError, UnknownJobStatusError, } from '../../common/errors/job.errors.js'; import { ValidationError } from '../../common/errors/application.errors.js'; /** * Centralized error handler that logs and re-throws appropriate errors */ export class ErrorHandler { /** * Standard try-catch wrapper for controller methods * @param operation - The async operation to execute * @param context - Context string for logging (e.g., 'updateAgentConfig') * @param fallbackErrorCode - ServerError code to use if no specific handling applies */ static async handleControllerError<T>( operation: () => Promise<T> | T, context: string, fallbackErrorCode?: string ): Promise<T> { try { return await operation(); } catch (error) { logger.error(`Error in ${context}:`, error); // Re-throw known exceptions as-is if ( error instanceof HttpException || error instanceof ServerError || error instanceof WsException ) { throw error; } // Handle job-specific errors if (error instanceof JobNotFoundError) { throw new NotFoundException(error.message, { cause: error }); } if (error instanceof JobAccessDeniedError) { throw new ForbiddenException( 'Access denied: Job does not belong to user', { cause: error } ); } if (error instanceof JobNotCompletedError) { throw new BadRequestException(error.message, { cause: error }); } if (error instanceof JobFailedError) { throw new InternalServerErrorException(`Job failed: ${error.message}`, { cause: error, }); } if (error instanceof UnknownJobStatusError) { throw new InternalServerErrorException( `Unknown job status: ${error.message}`, { cause: error } ); } // Fallback error handling if (fallbackErrorCode) { throw new ServerError(fallbackErrorCode); } throw new InternalServerErrorException(`${context} failed`, { cause: error, }); } } /** * Handler for operations that should preserve BadRequestException but wrap others * @param operation - The async operation to execute * @param context - Context string for logging * @param errorMessage - Custom error message for wrapped exceptions */ static async handleWithBadRequestPreservation<T>( operation: () => Promise<T> | T, context: string, errorMessage?: string ): Promise<T> { try { return await operation(); } catch (error) { logger.error(`Error in ${context}:`, error); if (error instanceof BadRequestException) { throw error; } const message = errorMessage || `${context} failed: ${error.message}`; throw new BadRequestException(message); } } /** * WebSocket-specific error handler * @param operation - The async operation to execute * @param context - Context string for logging * @param client - WebSocket client for emitting errors * @param eventName - Event name to emit on error * @param fallbackErrorCode - ServerError code for unexpected errors */ static async handleWebSocketError<T>( operation: () => Promise<T> | T, context: string, client: { emit(event: string, payload: unknown): void }, eventName: string, fallbackErrorCode: string = 'E01TA400' ): Promise<T | void> { try { return await operation(); } catch (error) { if (error instanceof ServerError) { client.emit(eventName, error); return; } if (error instanceof WsException) { const reason = (typeof (error as any).getError === 'function' ? (error as any).getError() : undefined) ?? error.message ?? 'Validation failed'; const validationError = new ValidationError(String(reason)); client.emit(eventName, validationError); return; } logger.error(`Unexpected error in ${context}:`, error); const serverError = new ServerError(fallbackErrorCode); client.emit(eventName, serverError); } } } /** * Utility class for creating standardized response objects */ export class ResponseFormatter { /** * Creates a success response with the AgentResponse format * @param data - The data to include in the response */ static success<T>(data: T): { status: 'success'; data: T } { return { status: 'success', data, }; } /** * Creates a failure response with the AgentResponse format * @param error - The error message or details * @param data - Optional data to include with the error */ static failure<T = unknown>( error: string, data?: T ): { status: 'failure'; error: string; data?: T } { const response: { status: 'failure'; error: string; data?: T } = { status: 'failure', error, }; if (data !== undefined) { response.data = data; } return response; } /** * Creates a waiting for human input response with the AgentResponse format * @param data - Optional data to include */ static waitingForHumanInput<T = unknown>( data?: T ): { status: 'waiting_for_human_input'; data?: T } { const response: { status: 'waiting_for_human_input'; data?: T } = { status: 'waiting_for_human_input', }; if (data !== undefined) { response.data = data; } return response; } } /** * Decorator for automatic error handling in controller methods */ export function HandleErrors(errorCode?: string) { return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { return ErrorHandler.handleControllerError( () => originalMethod.apply(this, args), `${target.constructor.name}.${propertyKey}`, errorCode ); }; return descriptor; }; } /** * Decorator for handling operations that should preserve BadRequestException */ export function HandleWithBadRequestPreservation(errorMessage?: string) { return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod = descriptor.value; descriptor.value = async function (...args: any[]) { return ErrorHandler.handleWithBadRequestPreservation( () => originalMethod.apply(this, args), `${target.constructor.name}.${propertyKey}`, errorMessage ); }; return descriptor; }; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/KasarLabs/snak'

If you have feedback or need assistance with the MCP directory API, please join our Discord server