115 lines
3.1 KiB
TypeScript
115 lines
3.1 KiB
TypeScript
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<string, unknown>;
|
|
/** 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<string, string> {
|
|
const errors: Record<string, string> = {};
|
|
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;
|
|
}
|