@smartsamurai/krapi-sdk
Version:
KRAPI TypeScript SDK - Easy-to-use client SDK for connecting to self-hosted KRAPI servers (like Appwrite SDK)
238 lines (210 loc) • 6.54 kB
text/typescript
/**
* HTTP Error Class for KRAPI SDK
*
* Provides detailed error information for HTTP requests including:
* - HTTP status codes
* - Request details (method, URL, headers)
* - Response details (status, data)
* - Error categorization (network, API, auth, etc.)
*
* @module http-clients/http-error
*/
/**
* HTTP Error Class
*
* Extended Error class with HTTP-specific error information.
*
* @class HttpError
* @extends {Error}
*/
export class HttpError extends Error {
/**
* HTTP status code (if available)
*/
public readonly status?: number;
/**
* HTTP method (GET, POST, PUT, DELETE, etc.)
*/
public readonly method?: string;
/**
* Request URL
*/
public readonly url?: string;
/**
* Request headers (sanitized - no sensitive data)
*/
public readonly requestHeaders?: Record<string, string>;
/**
* Request body/data sent (if available)
*/
public readonly requestBody?: unknown;
/**
* Request query parameters (if available)
*/
public readonly requestQuery?: Record<string, unknown>;
/**
* Response data from the server (if available)
*/
public readonly responseData?: unknown;
/**
* Response headers (if available)
*/
public readonly responseHeaders?: Record<string, string>;
/**
* Error code for programmatic error handling
*/
public readonly code?: string;
/**
* Whether this is an API error (4xx, 5xx)
*/
public readonly isApiError: boolean;
/**
* Whether this is a network error (connection failed, timeout, etc.)
*/
public readonly isNetworkError: boolean;
/**
* Whether this is an authentication error (401, 403)
*/
public readonly isAuthError: boolean;
/**
* Whether this is a client error (4xx)
*/
public readonly isClientError: boolean;
/**
* Whether this is a server error (5xx)
*/
public readonly isServerError: boolean;
/**
* Original error object (if wrapped)
*/
public readonly originalError?: unknown;
/**
* Create a new HttpError instance
*
* @param {string} message - Error message
* @param {Object} options - Error options
* @param {number} [options.status] - HTTP status code
* @param {string} [options.method] - HTTP method
* @param {string} [options.url] - Request URL
* @param {Record<string, string>} [options.requestHeaders] - Request headers
* @param {unknown} [options.requestBody] - Request body/data sent
* @param {Record<string, unknown>} [options.requestQuery] - Request query parameters
* @param {unknown} [options.responseData] - Response data
* @param {Record<string, string>} [options.responseHeaders] - Response headers
* @param {string} [options.code] - Error code
* @param {unknown} [options.originalError] - Original error
*/
constructor(
message: string,
options: {
status?: number;
method?: string;
url?: string;
requestHeaders?: Record<string, string>;
requestBody?: unknown;
requestQuery?: Record<string, unknown>;
responseData?: unknown;
responseHeaders?: Record<string, string>;
code?: string;
originalError?: unknown;
} = {}
) {
super(message);
this.name = "HttpError";
// Set properties (only if defined to avoid undefined assignment)
if (options.status !== undefined) {
this.status = options.status;
}
if (options.method !== undefined) {
this.method = options.method;
}
if (options.url !== undefined) {
this.url = options.url;
}
if (options.requestHeaders !== undefined) {
this.requestHeaders = options.requestHeaders;
}
if (options.requestBody !== undefined) {
this.requestBody = options.requestBody;
}
if (options.requestQuery !== undefined) {
this.requestQuery = options.requestQuery;
}
if (options.responseData !== undefined) {
this.responseData = options.responseData;
}
if (options.responseHeaders !== undefined) {
this.responseHeaders = options.responseHeaders;
}
if (options.code !== undefined) {
this.code = options.code;
}
if (options.originalError !== undefined) {
this.originalError = options.originalError;
}
// Categorize error
this.isApiError = options.status !== undefined && options.status >= 400;
this.isNetworkError = options.status === undefined;
this.isAuthError = options.status === 401 || options.status === 403;
this.isClientError = options.status !== undefined && options.status >= 400 && options.status < 500;
this.isServerError = options.status !== undefined && options.status >= 500;
// Maintain proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, HttpError);
}
}
/**
* Get a detailed error message with all available information
*
* @returns {string} Detailed error message
*/
getDetailedMessage(): string {
const parts: string[] = [this.message];
if (this.status) {
parts.push(`(HTTP ${this.status})`);
}
if (this.method && this.url) {
parts.push(`[${this.method} ${this.url}]`);
}
if (this.code) {
parts.push(`[Code: ${this.code}]`);
}
// Add response data if available and is an object with error/message
if (this.responseData && typeof this.responseData === "object") {
const response = this.responseData as Record<string, unknown>;
if (response.error && typeof response.error === "string") {
parts.push(`Backend error: ${response.error}`);
} else if (response.message && typeof response.message === "string") {
parts.push(`Backend message: ${response.message}`);
}
}
return parts.join(" ");
}
/**
* Convert error to JSON for logging
*
* @returns {Record<string, unknown>} JSON representation
*/
toJSON(): Record<string, unknown> {
return {
name: this.name,
message: this.message,
status: this.status,
method: this.method,
url: this.url,
code: this.code,
isApiError: this.isApiError,
isNetworkError: this.isNetworkError,
isAuthError: this.isAuthError,
isClientError: this.isClientError,
isServerError: this.isServerError,
requestHeaders: this.requestHeaders,
requestBody: this.requestBody,
requestQuery: this.requestQuery,
responseData: this.responseData,
responseHeaders: this.responseHeaders,
originalError: this.originalError,
stack: this.stack,
};
}
}