import type { ApiError, ValidationDetail, ErrorCodeType } from './types'; /** * API client error with typed error information. * Provides convenient methods for accessing validation errors. */ export class ApiClientError extends Error { /** HTTP status code */ readonly status: number; /** Machine-readable error code */ readonly code: string; /** Optional validation details or other error details */ readonly details?: ValidationDetail[] | Record; /** The original response */ readonly response?: Response; constructor(apiError: ApiError, response?: Response) { super(apiError.message); this.name = 'ApiClientError'; this.status = apiError.status; this.code = apiError.code; this.details = apiError.details; this.response = response; // Maintains proper stack trace in V8 environments if ('captureStackTrace' in Error && typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, ApiClientError); } } /** * Check if this is a validation error. */ isValidationError(): boolean { return this.code === 'VALIDATION_ERROR' || this.code === 'BAD_REQUEST'; } /** * Check if this is an authentication error. */ isAuthError(): boolean { return this.status === 401 || this.code === 'UNAUTHORIZED'; } /** * Check if this is a permission error. */ isForbiddenError(): boolean { return this.status === 403 || this.code === 'FORBIDDEN'; } /** * Check if this is a not found error. */ isNotFoundError(): boolean { return this.status === 404 || this.code === 'NOT_FOUND'; } /** * Check if the error has a specific code. */ hasCode(code: ErrorCodeType | string): boolean { return this.code === code; } /** * Get validation errors as an array. * Returns empty array if no validation details are present. */ getValidationErrors(): ValidationDetail[] { if (!this.details) return []; if (Array.isArray(this.details)) return this.details; return []; } /** * Get validation errors mapped by field name. * Useful for displaying errors next to form fields. * * @example * const errors = error.getFieldErrors(); * // { email: 'email is required', password: 'password must be at least 8 characters' } */ getFieldErrors(): Record { const errors: Record = {}; for (const detail of this.getValidationErrors()) { errors[detail.field] = detail.message; } return errors; } /** * Get the error message for a specific field. * Returns undefined if no error for that field. */ getFieldError(field: string): string | undefined { const errors = this.getValidationErrors(); return errors.find((e) => e.field === field)?.message; } /** * Check if a specific field has an error. */ hasFieldError(field: string): boolean { return this.getFieldError(field) !== undefined; } } /** * Type guard for ApiClientError. */ export function isApiClientError(error: unknown): error is ApiClientError { return error instanceof ApiClientError; }