Skip to main content

Error Handling in BFAPIClient

This document provides a comprehensive guide to the standardized error handling system in BFAPIClient.

Overviewโ€‹

BFAPIClient includes a powerful error handling system that provides:

  1. Consistent Error Structure: All errors follow the same pattern with rich metadata
  2. Domain-Specific Error Classes: Error types specific to different parts of the system
  3. Error Translation: Utilities to convert various error types to our standardized format
  4. Error Code System: Unique error codes for programmatic handling
  5. Error Categories: Grouping of related errors for easier handling
  6. Retryable Error Detection: Built-in support for identifying which errors are safe to retry
  7. Severity Levels: Errors are classified by severity for appropriate handling
  8. Rich Context: Includes request/response data, error metadata, and original error cause

Error Classes Hierarchyโ€‹

BFError (base class)
โ”œโ”€โ”€ NetworkError
โ”œโ”€โ”€ TimeoutError
โ”œโ”€โ”€ APIError
โ”‚ โ””โ”€โ”€ RateLimitError
โ”‚ โ””โ”€โ”€ RequestValidationError
โ”œโ”€โ”€ AuthError
โ”‚ โ””โ”€โ”€ TokenError
โ”œโ”€โ”€ GraphQLError
โ”œโ”€โ”€ CacheError
โ”‚ โ””โ”€โ”€ CacheMissError
โ”œโ”€โ”€ CircuitBreakerError
โ”œโ”€โ”€ AIError
โ”‚ โ””โ”€โ”€ ContentFilterError
โ”‚ โ””โ”€โ”€ TokenLimitError
โ”œโ”€โ”€ MCPError
โ”‚ โ””โ”€โ”€ ToolExecutionError
โ”‚ โ””โ”€โ”€ AgentExecutionError
โ”œโ”€โ”€ ValidationError
โ””โ”€โ”€ TelemetryError

Additionally, utility classes like BatchPartialError<T> provide specialized error handling for specific scenarios.

Using the Error Systemโ€‹

Basic Error Handlingโ€‹

When making API calls, you can catch and handle errors as follows:

import [BFError] from '@bluefly/api-client';

try {
const response = await apiClient.get('/api/users/123');
// Process successful response
} catch (error) {
if (error instanceof BFError) {
// This is a standardized error from our system
console.error(`Error [error.code]: [error.message]`);

// Access rich error information
if (error.status) {
console.error(`HTTP Status: [error.status]`);
}

if (error.response?.data) {
console.error('Error response:', error.response.data);
}
} else {
// This is an unexpected error type
console.error('Unexpected error:', error);
}
}

Type-Based Error Handlingโ€‹

import { 
isNetworkError,
isServerError,
isAuthError,
ErrorCode
} from '@bluefly/api-client';

try {
const result = await apiClient.get('/api/resource');
processResult(result);
} catch (error) {
if (isNetworkError(error)) {
// Handle network connectivity issues
showOfflineMessage();
} else if (isServerError(error)) {
// Handle server errors (5xx)
logServerError(error);
showServerErrorMessage();
} else if (isAuthError(error)) {
// Handle authentication issues
redirectToLogin();
} else {
// Handle other errors
showGenericErrorMessage(error.message);
}
}

Checking for Specific Error Codesโ€‹

import { isErrorCode, ErrorCode } from '@bluefly/api-client';

try {
await apiClient.post('/api/resource', data);
} catch (error) {
if (isErrorCode(error, ErrorCode.API_RATE_LIMIT)) {
// Handle rate limiting
const retryAfter = error.response?.headers['retry-after'];
scheduleRetry(retryAfter);
} else if (isErrorCode(error, ErrorCode.API_BAD_REQUEST)) {
// Handle validation errors
showValidationErrors(error.response?.data?.errors);
}
}

Using Error Categoriesโ€‹

import { isErrorCategory, ErrorCategory } from '@bluefly/api-client';

try {
await apiClient.post('/api/resource', data);
} catch (error) {
if (isErrorCategory(error, ErrorCategory.AUTHENTICATION)) {
// Handle all authentication-related errors
handleAuthErrors(error);
} else if (isErrorCategory(error, ErrorCategory.RESILIENCE)) {
// Handle resilience-related errors (circuit breaker, etc.)
handleResilienceErrors(error);
}
}

Error Translationโ€‹

If you have your own error types that you want to convert to our standardized format:

import { translateError, ErrorCategory } from '@bluefly/api-client';

try {
// Some operation that might throw non-standard errors
await thirdPartyOperation();
} catch (originalError) {
// Convert to standardized error format
const standardError = translateError(originalError);

// Now you can use all the standard error handling utilities
if (standardError.category === ErrorCategory.API) {
// Handle API errors
}
}

Working with Specialized Error Typesโ€‹

For specialized error scenarios, use the specific error classes:

import { RateLimitError, TokenLimitError, ContentFilterError } from '@bluefly/api-client';

try {
await apiClient.post('/api/generate', { prompt: 'Generate content...' });
} catch (error) {
if (error instanceof RateLimitError) {
const delay = error.getRetryDelay();
console.log(`Rate limited. Retry after [delay]ms`);
// Schedule retry after the recommended delay
setTimeout(() => retry(), delay);
}
else if (error instanceof TokenLimitError) {
const excess = error.getExcessTokens();
console.error(`Token limit exceeded by [excess] tokens`);
// Reduce input size and try again
}
else if (error instanceof ContentFilterError) {
const categories = error.getFilterCategories();
console.error(`Content filtered. Triggered categories: [categories.join(', ')]`);
// Modify content to comply with guidelines
}
}

Error Propertiesโ€‹

All errors in the system extend the base BFError class, which includes:

PropertyTypeDescription
messagestringHuman-readable error message
codeErrorCodeUnique error code for programmatic handling
categoryErrorCategoryError category for grouping similar errors
severityErrorSeveritySeverity level of the error (DEBUG, INFO, WARNING, ERROR, CRITICAL)
statusnumber (optional)HTTP status code, if applicable
causeError (optional)Original error that caused this error
detailsobject (optional)Additional details about the error
requestErrorRequestInfo (optional)Request information (url, method, headers, data, etc.)
responseErrorResponseInfo (optional)Response information (status, headers, data, etc.)
metadataErrorMetadata (optional)Additional metadata (requestId, traceId, userId, etc.)
retryablebooleanWhether this error is safe to retry
timestampnumberWhen the error occurred (milliseconds since epoch)

Error Codesโ€‹

The system includes a comprehensive set of error codes organized by category:

General Errors (1000-1999)โ€‹

  • UNKNOWN_ERROR: Unknown or unspecified error
  • INVALID_INPUT: Invalid input or parameters
  • TIMEOUT_ERROR: Request timeout
  • NETWORK_ERROR: Network connectivity issues
  • PARSE_ERROR: Error parsing response data

Authentication Errors (2000-2999)โ€‹

  • AUTH_ERROR: Generic authentication error
  • AUTH_EXPIRED: Authentication token expired
  • AUTH_INSUFFICIENT: Insufficient permissions
  • AUTH_INVALID: Invalid authentication credentials

API Errors (3000-3999)โ€‹

  • API_ERROR: Generic API error
  • API_NOT_FOUND: Resource not found (404)
  • API_RATE_LIMIT: Rate limit exceeded (429)
  • API_SERVER_ERROR: Server error (5xx)
  • API_BAD_REQUEST: Bad request (400)
  • API_FORBIDDEN: Forbidden (403)
  • API_UNAVAILABLE: Service unavailable

Cache Errors (4000-4999)โ€‹

  • CACHE_ERROR: Generic cache error
  • CACHE_MISS: Cache miss
  • CACHE_INVALID: Invalid cache entry

Circuit Breaker Errors (5000-5999)โ€‹

  • CIRCUIT_OPEN: Circuit breaker is open

GraphQL Errors (6000-6999)โ€‹

  • GRAPHQL_ERROR: Generic GraphQL error
  • GRAPHQL_VALIDATION: GraphQL validation error

AI-specific Errors (7000-7999)โ€‹

  • AI_ERROR: Generic AI error
  • AI_CONTENT_FILTER: Content filtered by AI safety system
  • AI_MODEL_ERROR: AI model execution error
  • AI_CONTEXT_LIMIT: Context limit exceeded

MCP-specific Errors (8000-8999)โ€‹

  • MCP_ERROR: Generic MCP error
  • MCP_TOOL_NOT_FOUND: MCP tool not found
  • MCP_EXECUTION_ERROR: MCP tool execution error

Advanced Usageโ€‹

Creating Custom Error Instancesโ€‹

You can create your own error instances when needed:

import { BFError, ErrorCode, ErrorCategory, ErrorSeverity } from '@bluefly/api-client';

// Create a custom error for a business logic failure
const error = new BFError({
message: 'Failed to process payment',
code: ErrorCode.API_BAD_REQUEST,
category: ErrorCategory.API,
severity: ErrorSeverity.ERROR,
details: {
paymentId: '123456',
failureReason: 'Insufficient funds'
},
metadata: {
requestId: 'req-789',
userId: 'user-456'
}
});

// The error can be thrown or returned as needed
throw error;

Using Error Formattersโ€‹

import { BFError, formatError } from '@bluefly/api-client';

try {
await apiClient.post('/api/process', data);
} catch (error) {
if (error instanceof BFError) {
// Format error for logging with specific options
const formattedError = formatError(error, {
includeStack: true,
includeCause: true,
includeMetadata: true,
includeRequest: false,
includeResponse: true,
maxDetailLength: 500
});

console.error('Operation failed:', formattedError);
}
}

Working with Error Interceptorsโ€‹

import { BFError, ErrorCode } from '@bluefly/api-client';

// Add a custom error interceptor to transform errors
client.addErrorInterceptor(async (error) => {
if (error instanceof BFError && error.code === ErrorCode.AUTH_EXPIRED) {
// Try to refresh the token
try {
await refreshToken();

// Signal that the request should be retried
return {
data: null,
status: 0,
statusText: 'Token refreshed, retrying',
headers: {},
_retry: true
};
} catch (refreshError) {
// If token refresh fails, add context to the original error
return error.withContext({
refreshFailed: true,
refreshError: refreshError.message
});
}
}
return error;
});

Best Practicesโ€‹

  1. Always use try/catch: Wrap API calls in try/catch blocks to handle errors
  2. Check for specific error types: Use the helper functions to check for specific error types
  3. Log rich error data: The errors contain rich metadata that can be useful for debugging
  4. Handle common error scenarios: Implement handling for common error scenarios (network issues, authentication, etc.)
  5. Use error categories: Group error handling by category for more maintainable code
  6. Provide user-friendly messages: Translate error codes to user-friendly messages
  7. Implement retry logic for retryable errors: Use isRetryableError to determine if an error should be retried
  8. Add context to errors: Use withContext to add additional information to errors as they propagate
  9. Respect retry-after headers: When handling rate limiting, respect the retry-after headers
  10. Use specialized error types: Take advantage of the specialized error types for different scenarios

Migration from Legacy Error Handlingโ€‹

If you're using the legacy error system:

// Legacy error handling
try {
await client.get('/api/resource');
} catch (error) [if (error.status === 401) {
// Handle unauthorized] else if (error.code === 'ECONNABORTED') [// Handle timeout]
}

You can migrate to the new system:

// New error handling
try {
await client.get('/api/resource');
} catch (error) {
if (isErrorCode(error, ErrorCode.AUTH_INVALID)) {
// Handle unauthorized
} else if (isTimeoutError(error)) [// Handle timeout]
}

The new system provides more context and easier error handling.

Real-World Examplesโ€‹

Implementing a Resilient API Clientโ€‹

import { 
BFError,
isRetryableError,
RateLimitError,
NetworkError,
TimeoutError,
retryInterceptor,
createAdvancedRetryInterceptor,
CircuitBreaker
} from '@bluefly/api-client';

// Create a client with resilient error handling
const client = new RestClient({
baseUrl: 'https://api.example.com',
});

// Add basic retry logic for retryable errors
client.addErrorInterceptor(retryInterceptor({
maxRetries: 3,
retryDelay: 1000,
useExponentialBackoff: true
}));

// Add circuit breaker for critical endpoints
const circuitBreaker = new CircuitBreaker({
failureThreshold: 5,
resetTimeout: 30000
});

// Implement resilient request function
async function resilientRequest<T>(url: string, method: string, data?: any): Promise<T> {
try {
// Check circuit breaker state before making the request
circuitBreaker.checkState(url);

// Make the request
const response = await client.request(url, method, { data });

// Record success with the circuit breaker
circuitBreaker.recordSuccess(url);

return response.data;
} catch (error) {
// Record failure with the circuit breaker if it's a server error
if (error instanceof BFError && error.status && error.status >= 500) {
circuitBreaker.recordFailure(url);
}

if (error instanceof RateLimitError) {
// Handle rate limiting with the specialized helper
const delay = error.getRetryDelay();
console.warn(`Rate limited. Retry after [delay]ms`);
// Could implement automatic retry after delay
await new Promise(resolve => setTimeout(resolve, delay));
return resilientRequest(url, method, data);
}

if (error instanceof NetworkError || error instanceof TimeoutError) {
console.warn(`Connection issue: [error.message]. Retrying...`);
// Could add progressive backoff retry
await new Promise(resolve => setTimeout(resolve, 2000));
return resilientRequest(url, method, data);
}

// Re-throw other errors
throw error;
}
}

Handling AI-Specific Errorsโ€‹

import { 
AIError,
TokenLimitError,
ContentFilterError,
ErrorCode
} from '@bluefly/api-client';

async function generateWithAI(prompt: string, options: any = {}) {
try {
const response = await apiClient.post('/api/ai/generate', { prompt, ...options });
return response.data;
} catch (error) {
if (error instanceof TokenLimitError) {
// Handle token limit exceeded
const excess = error.getExcessTokens() || 0;

if (excess > 1000) {
// Significant excess, truncate prompt substantially
return generateWithAI(prompt.slice(0, prompt.length / 2), options);
} else {
// Minor excess, trim prompt slightly
return generateWithAI(prompt.slice(0, prompt.length - excess - 100), options);
}
}

if (error instanceof ContentFilterError) {
// Handle content filter
const categories = error.getFilterCategories();
console.error(`Content filtered due to: [categories.join(', ')]`);

// Could implement alternative prompting strategy
if (categories.includes('hate') || categories.includes('violence')) {
throw new Error('Cannot process request with harmful content');
}

// For other categories, might try to sanitize the prompt
return generateWithAI(sanitizePrompt(prompt), options);
}

if (error instanceof AIError) {
// Handle other AI-specific errors
console.error(`AI error: [error.message] [Model: [error.getModel() || 'unknown']]`);

// Could implement fallback to different model
if (options.model === 'gpt-4' && error.code === ErrorCode.AI_MODEL_UNAVAILABLE) {
return generateWithAI(prompt, { ...options, model: 'gpt-3.5-turbo' });
}
}

// Re-throw other errors
throw error;
}
}

function sanitizePrompt(prompt: string): string {
// Implement content sanitization logic
return prompt.replace(/offensive terms|inappropriate content/gi, '***');
}