integratedhub/utils/errorHandler.ts
2026-02-14 02:20:19 +00:00

175 lines
4.5 KiB
TypeScript

// utils/errorHandler.ts
export interface ApiError {
code: string;
message: string;
details?: string;
field?: string;
}
export class ValidationError extends Error {
public field?: string;
constructor(msg: string, field?: string) {
super(msg);
this.name = "ValidationError";
this.field = field;
}
}
export class NetworkError extends Error {
constructor(msg: string) {
super(msg);
this.name = "NetworkError";
}
}
export function createValidationErrorResponse(
msg: string,
field?: string,
): Response {
const error: ApiError = {
code: "VALIDATION_ERROR",
message: msg,
...(field && { field }),
};
return new Response(JSON.stringify(error), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
export function createNetworkErrorResponse(
msg: string = "Network error",
): Response {
return new Response(JSON.stringify({ code: "NETWORK_ERROR", message: msg }), {
status: 503,
headers: { "Content-Type": "application/json" },
});
}
export function createTimeoutErrorResponse(ms: number): Response {
return new Response(
JSON.stringify({
code: "TIMEOUT_ERROR",
message: "Request timed out after " + ms + "ms. Please try again.",
}),
{ status: 504, headers: { "Content-Type": "application/json" } },
);
}
export function validateRequiredString(
value: unknown,
fieldName: string,
): void {
if (value === null || value === undefined || value === "") {
throw new ValidationError(fieldName + " is required", fieldName);
}
if (typeof value !== "string") {
throw new ValidationError(fieldName + " must be a string", fieldName);
}
}
export function validateNumber(
value: unknown,
fieldName: string,
opts?: { min?: number; max?: number; allowZero?: boolean },
): number {
if (value === null || value === undefined || value === "") {
throw new ValidationError(fieldName + " is required", fieldName);
}
const numValue = typeof value === "string"
? parseFloat(value)
: typeof value === "number"
? value
: NaN;
if (isNaN(numValue)) {
throw new ValidationError(fieldName + " must be a valid number", fieldName);
}
if (opts?.min !== undefined && numValue < opts.min) {
throw new ValidationError(
fieldName + " must be at least " + opts.min,
fieldName,
);
}
if (opts?.max !== undefined && numValue > opts.max) {
throw new ValidationError(
fieldName + " must be at most " + opts.max,
fieldName,
);
}
if (!opts?.allowZero && numValue === 0) {
throw new ValidationError(
fieldName + " must be greater than zero",
fieldName,
);
}
return numValue;
}
export function validateInteger(
value: unknown,
fieldName: string,
opts?: { min?: number; max?: number; positive?: boolean },
): number {
const numValue = validateNumber(value, fieldName, opts);
if (!Number.isInteger(numValue)) {
throw new ValidationError(fieldName + " must be a whole number", fieldName);
}
if (opts?.positive && numValue <= 0) {
throw new ValidationError(
fieldName + " must be a positive integer",
fieldName,
);
}
return numValue;
}
export function withTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
errMsg: string = "Timeout",
): Promise<T> {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error(errMsg)), timeoutMs)
),
]);
}
export function handleApiError(error: unknown): Response {
console.error("API Error:", error);
if (error instanceof ValidationError) {
return createValidationErrorResponse(error.message, error.field);
}
if (error instanceof NetworkError) {
return createNetworkErrorResponse(error.message);
}
if (error instanceof Error) {
if (
error.message.includes("timeout") || error.message.includes("timed out")
) {
return createTimeoutErrorResponse(30000);
}
if (
error.message.includes("network") || error.message.includes("connection")
) {
return createNetworkErrorResponse(error.message);
}
return new Response(
JSON.stringify({
code: "INTERNAL_ERROR",
message: "An unexpected error occurred",
details: error.message,
}),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
return new Response(
JSON.stringify({
code: "INTERNAL_ERROR",
message: "An unexpected error occurred",
}),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}