Files
the_order/packages/shared/src/error-handler.ts

148 lines
3.1 KiB
TypeScript
Raw Normal View History

/**
* Error handling utilities for The Order services
*/
import { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
/**
* Custom application error class
*/
export class AppError extends Error {
constructor(
public statusCode: number,
public code: string,
message: string,
public details?: unknown,
public retryable = false,
public timestamp = new Date()
) {
super(message);
this.name = 'AppError';
Error.captureStackTrace(this, this.constructor);
}
/**
* Check if error is retryable
*/
isRetryable(): boolean {
return this.retryable;
}
/**
* Get error context
*/
getContext(): Record<string, unknown> {
return {
statusCode: this.statusCode,
code: this.code,
message: this.message,
details: this.details,
retryable: this.retryable,
timestamp: this.timestamp.toISOString(),
};
}
}
/**
* Global error handler for Fastify
*/
export async function errorHandler(
error: FastifyError,
request: FastifyRequest,
reply: FastifyReply
): Promise<void> {
request.log.error({
err: error,
url: request.url,
method: request.method,
statusCode: error.statusCode || 500,
});
if (error instanceof AppError) {
return reply.status(error.statusCode).send({
error: {
code: error.code,
message: error.message,
details: error.details,
},
});
}
// Handle validation errors
if (error.validation) {
return reply.status(400).send({
error: {
code: 'VALIDATION_ERROR',
message: 'Validation failed',
details: error.validation,
},
});
}
// Don't expose internal errors in production
const isProduction = process.env.NODE_ENV === 'production';
const statusCode = error.statusCode || 500;
// Log error details for monitoring
request.log.error({
error: {
message: error.message,
stack: error.stack,
code: error.code || 'INTERNAL_ERROR',
statusCode,
},
request: {
method: request.method,
url: request.url,
headers: request.headers,
},
});
return reply.status(statusCode).send({
error: {
code: 'INTERNAL_ERROR',
message: isProduction ? 'Internal server error' : error.message,
...(isProduction ? {} : { stack: error.stack }),
...(error instanceof AppError && error.retryable ? { retryable: true } : {}),
},
});
}
/**
* Create a standardized error response
*/
export function createErrorResponse(
statusCode: number,
code: string,
message: string,
details?: unknown,
retryable = false
): AppError {
return new AppError(statusCode, code, message, details, retryable);
}
/**
* Create a retryable error
*/
export function createRetryableError(
statusCode: number,
code: string,
message: string,
details?: unknown
): AppError {
return new AppError(statusCode, code, message, details, true);
}
/**
* Create a non-retryable error
*/
export function createNonRetryableError(
statusCode: number,
code: string,
message: string,
details?: unknown
): AppError {
return new AppError(statusCode, code, message, details, false);
}