MicProtocol / apps /web /src /lib /error-utils.ts
Benaah's picture
Upload folder using huggingface_hub
fb12607 verified
Raw History Blame Contribute Delete
4.34 kB
import { TRPCClientError } from "@trpc/client";
/**
* Standard utility to parse errors from tRPC, HTTP responses, or native Error objects
* and extract user-friendly messages, status codes, and field validation errors.
*/
export interface FormattedError {
title: string;
message: string;
statusCode?: number;
code?: string;
requestId?: string;
fieldErrors?: Record<string, string>;
isNetworkError: boolean;
isRateLimit: boolean;
isAuthError: boolean;
isForbidden: boolean;
isNotFound: boolean;
}
export function parseError(error: unknown, fallbackMessage = "An unexpected error occurred"): FormattedError {
let title = "Error";
let message = fallbackMessage;
let statusCode: number | undefined;
let code: string | undefined;
let requestId: string | undefined;
let fieldErrors: Record<string, string> | undefined;
let isNetworkError = false;
if (!error) {
return {
title,
message,
isNetworkError: false,
isRateLimit: false,
isAuthError: false,
isForbidden: false,
isNotFound: false,
};
}
// Handle tRPC Client Error
if (error instanceof TRPCClientError || (typeof error === "object" && error !== null && "data" in error)) {
const trpcErr = error as any;
code = trpcErr.data?.code;
statusCode = trpcErr.data?.httpStatus;
if (trpcErr.message) {
message = trpcErr.message;
}
// Check for zod validation errors
if (trpcErr.data?.zodError) {
const zodData = trpcErr.data.zodError;
if (typeof zodData === "object" && zodData.fieldErrors) {
fieldErrors = {};
for (const [field, msgs] of Object.entries(zodData.fieldErrors)) {
if (Array.isArray(msgs) && msgs.length > 0) {
fieldErrors[field] = msgs[0] as string;
}
}
message = "Please check the highlighted fields and try again.";
title = "Validation Failed";
} else if (typeof zodData === "string") {
try {
const parsed = JSON.parse(zodData);
if (Array.isArray(parsed) && parsed.length > 0) {
message = parsed[0].message || message;
}
} catch {
message = zodData;
}
}
}
} else if (error instanceof Error) {
message = error.message;
// Check for network fetch failures
if (
message.includes("Failed to fetch") ||
message.includes("NetworkError") ||
message.includes("Load failed")
) {
isNetworkError = true;
title = "Connection Lost";
message = "Unable to connect to the server. Please verify your internet connection.";
}
} else if (typeof error === "string") {
message = error;
} else if (typeof error === "object" && error !== null) {
const errObj = error as Record<string, any>;
if (errObj.message) message = String(errObj.message);
if (errObj.code) code = String(errObj.code);
if (errObj.statusCode) statusCode = Number(errObj.statusCode);
if (errObj.requestId) requestId = String(errObj.requestId);
}
// Status code / Code specific titling & messages
const isRateLimit = statusCode === 429 || code === "TOO_MANY_REQUESTS" || message.toLowerCase().includes("rate limit");
const isAuthError = statusCode === 401 || code === "UNAUTHORIZED";
const isForbidden = statusCode === 403 || code === "FORBIDDEN";
const isNotFound = statusCode === 404 || code === "NOT_FOUND";
if (isRateLimit) {
title = "Rate Limit Reached";
message = "You have made too many requests. Please pause for a few seconds before trying again.";
} else if (isAuthError) {
title = "Authentication Required";
message = "Your session has expired or you are not logged in. Please sign in to continue.";
} else if (isForbidden) {
title = "Access Denied";
message = "You do not have permission to perform this action. You may need to upgrade your subscription plan or ask a workspace admin.";
} else if (isNotFound) {
title = "Resource Not Found";
}
return {
title,
message,
statusCode,
code,
requestId,
fieldErrors,
isNetworkError,
isRateLimit,
isAuthError,
isForbidden,
isNotFound,
};
}
export function formatErrorMessage(error: unknown, fallbackMessage?: string): string {
return parseError(error, fallbackMessage).message;
}