BillTracker/middleware/errorFormatter.cts

90 lines
2.8 KiB
TypeScript

/**
* Centralized Error Formatter Middleware
*
* Standard error response format:
* { "error", "message", "field", "code" }
*/
import type { Req, Res, Next } from '../types/http';
const { ValidationError, formatError } = require('../utils/apiError.cts');
/** Extract field name from various validation error patterns */
function extractFieldFromError(message: any): string | null {
if (!message) return null;
const fieldMatch = message.match(/^(\w+)\s+(?:must be|is|are)\s+/i);
if (fieldMatch) return fieldMatch[1];
const yearMonthMatch = message.match(
/^(year|month|due_day|interest_rate|actual_amount|start_year|start_month|end_year|end_month)\s+must\b/i,
);
if (yearMonthMatch) return yearMonthMatch[1];
const requiredMatch = message.match(
/^(username|password|name|bill_id|category_id|due_day)\s+(?:is|are)\s+required/i,
);
if (requiredMatch) return requiredMatch[1];
return null;
}
/** Convert a plain error message/string into a standardized error object */
function standardizeError(message: any, code = 'VALIDATION_ERROR', fieldOverride: any = null) {
const field = fieldOverride || extractFieldFromError(message);
return {
error: code,
message: typeof message === 'string' ? message : String(message),
field: field || null,
code: code,
};
}
/** Express middleware that ensures all error responses follow standard format */
function errorFormatter(req: Req, res: Res, next: Next): void {
const originalJson = res.json;
res.json = function (this: any, data: any) {
if (data && typeof data === 'object' && !Array.isArray(data) && data.error && !data.success) {
// Already standardized (machine-readable code + human message) — pass through
if (typeof data.code === 'string' && data.code && typeof data.message === 'string') {
return originalJson.call(this, data);
}
const message = typeof data.message === 'string' && data.message ? data.message : data.error;
const code = typeof data.code === 'string' && data.code ? data.code : 'ERROR';
const standardized = standardizeError(message, code, data.field);
return originalJson.call(this, standardized);
}
return originalJson.call(this, data);
};
next();
}
function formatValidationError(message: any, field?: any) {
return standardizeError(message, 'VALIDATION_ERROR', field);
}
function formatNotFoundError(message: any, field?: any) {
return standardizeError(message, 'NOT_FOUND', field);
}
function formatAuthError(message: any) {
return standardizeError(message, 'AUTH_ERROR');
}
function formatForbiddenError(message: any) {
return standardizeError(message, 'FORBIDDEN');
}
module.exports = {
errorFormatter,
standardizeError,
formatValidationError,
formatNotFoundError,
formatAuthError,
formatForbiddenError,
extractFieldFromError,
};