123 lines
2.8 KiB
JavaScript
123 lines
2.8 KiB
JavaScript
/**
|
|
* Centralized error handling utility for API routes
|
|
*
|
|
* Standard error format:
|
|
* {
|
|
* error: 'ErrorType',
|
|
* message: 'Human-readable description',
|
|
* field: 'optional-field-name',
|
|
* code: 'machine-readable-code'
|
|
* }
|
|
*/
|
|
|
|
class ApiError extends Error {
|
|
constructor(code, message, status = 500, details = {}) {
|
|
super(message);
|
|
this.name = 'ApiError';
|
|
this.code = code;
|
|
this.status = status;
|
|
this.details = details;
|
|
|
|
// Extract field name from details if provided
|
|
if (details.field) {
|
|
this.field = details.field;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a standardized validation error
|
|
*/
|
|
function ValidationError(message, field, code = 'VALIDATION_ERROR') {
|
|
return new ApiError(code, message, 400, { field });
|
|
}
|
|
|
|
/**
|
|
* Create a standardized authentication error
|
|
*/
|
|
function AuthError(message = 'Authentication required', code = 'AUTH_ERROR') {
|
|
return new ApiError(code, message, 401);
|
|
}
|
|
|
|
/**
|
|
* Create a standardized authorization error
|
|
*/
|
|
function ForbiddenError(message = 'Access denied', code = 'FORBIDDEN') {
|
|
return new ApiError(code, message, 403);
|
|
}
|
|
|
|
/**
|
|
* Create a standardized not found error
|
|
*/
|
|
function NotFoundError(message = 'Resource not found', code = 'NOT_FOUND') {
|
|
return new ApiError(code, message, 404);
|
|
}
|
|
|
|
/**
|
|
* Create a standardized conflict error
|
|
*/
|
|
function ConflictError(message = 'Resource conflict', code = 'CONFLICT') {
|
|
return new ApiError(code, message, 409);
|
|
}
|
|
|
|
/**
|
|
* Create a standardized rate limit error
|
|
*/
|
|
function RateLimitError(message = 'Too many requests', code = 'RATE_LIMITED') {
|
|
return new ApiError(code, message, 429);
|
|
}
|
|
|
|
/**
|
|
* Format an error for JSON response
|
|
* This ensures consistent error format across all routes
|
|
*/
|
|
function formatError(err) {
|
|
if (err instanceof ApiError) {
|
|
const output = {
|
|
error: err.code,
|
|
message: err.message,
|
|
};
|
|
if (err.field) output.field = err.field;
|
|
return output;
|
|
}
|
|
|
|
// Fallback for non-standard errors (log internally, show safe message)
|
|
const safeMessage = err.status === 500
|
|
? 'Internal server error'
|
|
: err.message || 'An error occurred';
|
|
|
|
return {
|
|
error: err.code || 'ERROR',
|
|
message: safeMessage,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Express middleware to handle errors centrally
|
|
*/
|
|
function errorHandler(err, req, res, next) {
|
|
const formatted = formatError(err);
|
|
const status = err.status || (err instanceof ApiError ? 500 : 500);
|
|
|
|
// Only log non-500 errors to avoid exposing sensitive info
|
|
if (status !== 500) {
|
|
console.error(`[error] ${formatted.error}: ${formatted.message}`);
|
|
}
|
|
|
|
if (!res.headersSent) {
|
|
res.status(status).json(formatted);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
ApiError,
|
|
ValidationError,
|
|
AuthError,
|
|
ForbiddenError,
|
|
NotFoundError,
|
|
ConflictError,
|
|
RateLimitError,
|
|
formatError,
|
|
errorHandler,
|
|
};
|