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:
- Consistent Error Structure: All errors follow the same pattern with rich metadata
- Domain-Specific Error Classes: Error types specific to different parts of the system
- Error Translation: Utilities to convert various error types to our standardized format
- Error Code System: Unique error codes for programmatic handling
- Error Categories: Grouping of related errors for easier handling
- Retryable Error Detection: Built-in support for identifying which errors are safe to retry
- Severity Levels: Errors are classified by severity for appropriate handling
- 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:
Property | Type | Description |
---|---|---|
message | string | Human-readable error message |
code | ErrorCode | Unique error code for programmatic handling |
category | ErrorCategory | Error category for grouping similar errors |
severity | ErrorSeverity | Severity level of the error (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
status | number (optional) | HTTP status code, if applicable |
cause | Error (optional) | Original error that caused this error |
details | object (optional) | Additional details about the error |
request | ErrorRequestInfo (optional) | Request information (url, method, headers, data, etc.) |
response | ErrorResponseInfo (optional) | Response information (status, headers, data, etc.) |
metadata | ErrorMetadata (optional) | Additional metadata (requestId, traceId, userId, etc.) |
retryable | boolean | Whether this error is safe to retry |
timestamp | number | When 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 errorINVALID_INPUT
: Invalid input or parametersTIMEOUT_ERROR
: Request timeoutNETWORK_ERROR
: Network connectivity issuesPARSE_ERROR
: Error parsing response data
Authentication Errors (2000-2999)โ
AUTH_ERROR
: Generic authentication errorAUTH_EXPIRED
: Authentication token expiredAUTH_INSUFFICIENT
: Insufficient permissionsAUTH_INVALID
: Invalid authentication credentials
API Errors (3000-3999)โ
API_ERROR
: Generic API errorAPI_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 errorCACHE_MISS
: Cache missCACHE_INVALID
: Invalid cache entry
Circuit Breaker Errors (5000-5999)โ
CIRCUIT_OPEN
: Circuit breaker is open
GraphQL Errors (6000-6999)โ
GRAPHQL_ERROR
: Generic GraphQL errorGRAPHQL_VALIDATION
: GraphQL validation error
AI-specific Errors (7000-7999)โ
AI_ERROR
: Generic AI errorAI_CONTENT_FILTER
: Content filtered by AI safety systemAI_MODEL_ERROR
: AI model execution errorAI_CONTEXT_LIMIT
: Context limit exceeded
MCP-specific Errors (8000-8999)โ
MCP_ERROR
: Generic MCP errorMCP_TOOL_NOT_FOUND
: MCP tool not foundMCP_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โ
- Always use try/catch: Wrap API calls in try/catch blocks to handle errors
- Check for specific error types: Use the helper functions to check for specific error types
- Log rich error data: The errors contain rich metadata that can be useful for debugging
- Handle common error scenarios: Implement handling for common error scenarios (network issues, authentication, etc.)
- Use error categories: Group error handling by category for more maintainable code
- Provide user-friendly messages: Translate error codes to user-friendly messages
- Implement retry logic for retryable errors: Use isRetryableError to determine if an error should be retried
- Add context to errors: Use withContext to add additional information to errors as they propagate
- Respect retry-after headers: When handling rate limiting, respect the retry-after headers
- 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, '***');
}