novel-reader-sdk
Version:
SDK for Novel Reader API
184 lines (149 loc) • 4.72 kB
text/typescript
/**
* SDK Error Classes
*
* Domain-specific error classes for client-side error handling
* Matches the service's error types for consistency
*/
import type { ErrorCode, ErrorType, ApiError } from './types';
export abstract class ScrapingServiceError extends Error {
abstract readonly code: ErrorCode;
abstract readonly type: ErrorType;
constructor(
message: string,
public override readonly cause?: unknown
) {
super(message);
this.name = this.constructor.name;
}
}
// ===== Client-Side Errors =====
export class NetworkError extends ScrapingServiceError {
readonly code = 'INTERNAL_SERVER_ERROR' as const;
readonly type = 'SCRAPING_ERROR' as const;
constructor(message: string, cause?: unknown) {
super(`Network error: ${message}`, cause);
}
}
export class TimeoutError extends ScrapingServiceError {
readonly code = 'TIMEOUT_ERROR' as const;
readonly type = 'TIMEOUT_ERROR' as const;
constructor(message: string, cause?: unknown) {
super(`Request timeout: ${message}`, cause);
}
}
export class ValidationError extends ScrapingServiceError {
readonly code = 'BAD_REQUEST' as const;
readonly type = 'VALIDATION_ERROR' as const;
constructor(message: string, cause?: unknown) {
super(`Validation error: ${message}`, cause);
}
}
// ===== Service-Side Errors (from API responses) =====
export class ChapterNotFoundError extends ScrapingServiceError {
readonly code = 'NOT_FOUND' as const;
readonly type = 'CHAPTER_NOT_FOUND' as const;
constructor(url: string) {
super(`Chapter not found at URL: ${url}`);
}
}
export class ContentNotFoundError extends ScrapingServiceError {
readonly code = 'NOT_FOUND' as const;
readonly type = 'CONTENT_NOT_FOUND' as const;
constructor(message: string) {
super(`Content not found: ${message}`);
}
}
export class UnsupportedSiteError extends ScrapingServiceError {
readonly code = 'BAD_REQUEST' as const;
readonly type = 'UNSUPPORTED_SITE' as const;
constructor(hostname: string) {
super(`Unsupported site: ${hostname}`);
}
}
export class BrowserError extends ScrapingServiceError {
readonly code = 'INTERNAL_SERVER_ERROR' as const;
readonly type = 'BROWSER_ERROR' as const;
constructor(message: string) {
super(`Browser error: ${message}`);
}
}
export class RateLimitError extends ScrapingServiceError {
readonly code = 'RATE_LIMIT_EXCEEDED' as const;
readonly type = 'RATE_LIMIT_ERROR' as const;
constructor(message: string) {
super(`Rate limit exceeded: ${message}`);
}
}
export class ServiceError extends ScrapingServiceError {
readonly code = 'INTERNAL_SERVER_ERROR' as const;
readonly type = 'SCRAPING_ERROR' as const;
constructor(message: string, cause?: unknown) {
super(`Service error: ${message}`, cause);
}
}
// ===== Error Mapping and Utilities =====
const errorTypeToClass = {
SCRAPING_ERROR: ServiceError,
CHAPTER_NOT_FOUND: ChapterNotFoundError,
CONTENT_NOT_FOUND: ContentNotFoundError,
UNSUPPORTED_SITE: UnsupportedSiteError,
BROWSER_ERROR: BrowserError,
TIMEOUT_ERROR: TimeoutError,
VALIDATION_ERROR: ValidationError,
RATE_LIMIT_ERROR: RateLimitError,
} as const;
/**
* Creates appropriate error instance from API error response
*/
export function createErrorFromApiResponse(apiError: ApiError): ScrapingServiceError {
const { code, message } = apiError.error;
const ErrorClass = errorTypeToClass[code as ErrorType];
if (ErrorClass) {
return new ErrorClass(message);
}
// Fallback to generic service error
return new ServiceError(`API Error [${code}]: ${message}`);
}
/**
* Type guard to check if error is a scraping service error
*/
export function isScrapingServiceError(error: unknown): error is ScrapingServiceError {
return error instanceof ScrapingServiceError;
}
/**
* Type guard to check if error is a specific error type
*/
export function isErrorOfType<T extends ScrapingServiceError>(
error: unknown,
ErrorClass: new (...args: unknown[]) => T
): error is T {
return error instanceof ErrorClass;
}
/**
* Extract error message from various error types
*/
export function getErrorMessage(error: unknown): string {
if (isScrapingServiceError(error)) {
return error.message;
}
if (error instanceof Error) {
return error.message;
}
if (typeof error === 'string') {
return error;
}
return 'Unknown error occurred';
}
/**
* HTTP status code mapping for errors
*/
export const httpStatusMap: Record<ErrorCode, number> = {
INTERNAL_SERVER_ERROR: 500,
NOT_FOUND: 404,
BAD_REQUEST: 400,
UNSUPPORTED_SITE: 400,
CONTENT_NOT_FOUND: 404,
BROWSER_ERROR: 500,
TIMEOUT_ERROR: 408,
RATE_LIMIT_EXCEEDED: 429,
};