daisy-client / src /lib /errors.ts
Jessedbrown's picture
feat/init-daisy-client
6d643ca
Raw History Blame Contribute Delete
4.38 kB
import { AxiosError } from 'axios';
import { toast } from 'sonner';
import { ERROR_CODES, HTTP_STATUS } from '@/utils/constants';
import { CACHE_CONFIG } from './config';
export interface AppError {
message: string;
code?: string;
statusCode?: number;
details?: Record<string, unknown>;
}
export class ValidationError extends Error {
constructor(
message: string,
public field?: string
) {
super(message);
this.name = 'ValidationError';
}
}
export class NetworkError extends Error {
constructor(
message: string,
public statusCode?: number
) {
super(message);
this.name = 'NetworkError';
}
}
export class ApiError extends Error {
constructor(
message: string,
public statusCode?: number,
public code?: string,
public details?: Record<string, unknown>
) {
super(message);
this.name = 'ApiError';
}
}
// Error parsing utility
export const parseError = (error: unknown): AppError => {
// Axios error
if (error instanceof AxiosError) {
const statusCode = error.response?.status;
const responseData = error.response?.data as Record<string, unknown>;
const message =
typeof responseData?.message === 'string'
? responseData.message
: error.message;
const code =
typeof responseData?.code === 'string' ? responseData.code : error.code;
return {
message,
code,
statusCode,
details: responseData?.details as Record<string, unknown>,
};
}
if (error instanceof ValidationError) {
return {
message: error.message,
code: ERROR_CODES.VALIDATION_ERROR,
details: error.field ? { field: error.field } : undefined,
};
}
if (error instanceof NetworkError) {
return {
message: error.message,
code: ERROR_CODES.NETWORK_ERROR,
statusCode: error.statusCode,
};
}
if (error instanceof ApiError) {
return {
message: error.message,
code: error.code ?? ERROR_CODES.API_ERROR,
statusCode: error.statusCode,
details: error.details,
};
}
if (error instanceof Error) {
return {
message: error.message,
code: ERROR_CODES.UNKNOWN_ERROR,
};
}
return {
message: 'An unexpected error occurred',
code: ERROR_CODES.UNKNOWN_ERROR,
};
};
export const handleMutationError = (
error: unknown,
defaultMessage = 'Operation failed'
) => {
const parsedError = parseError(error);
const userMessage = getErrorMessage(error);
toast.error(userMessage || defaultMessage);
console.error('Mutation error:', parsedError);
return parsedError;
};
export const handleQueryError = (error: unknown, context = 'Loading data') => {
const parsedError = parseError(error);
const userMessage = getErrorMessage(error);
console.error('Query error:', parsedError);
toast.error(`${context} failed: ${userMessage}`);
return parsedError;
};
export const handleMutationSuccess = (message: string, data?: unknown) => {
toast.success(message);
console.info('Mutation success:', { message, data });
};
// Retry logic for network errors
export const shouldRetry = (error: unknown, attemptNumber: number): boolean => {
if (attemptNumber >= CACHE_CONFIG.RETRY.MAX_QUERY_RETRIES) return false;
const parsedError = parseError(error);
// Don't retry client errors (4xx)
if (
parsedError.statusCode &&
parsedError.statusCode >= HTTP_STATUS.BAD_REQUEST &&
parsedError.statusCode < HTTP_STATUS.INTERNAL_SERVER_ERROR
) {
return false;
}
// Retry network errors and server errors (5xx)
return true;
};
// User-friendly error messages
export const getErrorMessage = (error: unknown): string => {
const parsedError = parseError(error);
switch (parsedError.code) {
case ERROR_CODES.NETWORK_ERROR:
return 'Network connection failed. Please check your internet connection.';
case ERROR_CODES.VALIDATION_ERROR:
return parsedError.message;
case ERROR_CODES.UNAUTHORIZED:
return 'You are not authorized to perform this action.';
case ERROR_CODES.FORBIDDEN:
return 'Access denied.';
case ERROR_CODES.NOT_FOUND:
return 'The requested resource was not found.';
case ERROR_CODES.RATE_LIMIT:
return 'Too many requests. Please try again later.';
default:
return parsedError.message || 'An unexpected error occurred';
}
};