UNPKG

@ritas-inc/hanaqueryapi-client

Version:

TypeScript client for HANA Query API with full type safety and error handling

542 lines (449 loc) • 17.5 kB
/** * Error Handling Examples * * This file demonstrates comprehensive error handling strategies * for the HANA Query API client. */ import { HanaQueryClient, HanaQueryClientError, NetworkError, TimeoutError, ValidationError, AuthorizationError, NotFoundError, ServerError, UnknownError, isRetryableError, getRetryDelay, isNetworkError, isTimeoutError, isValidationError, isNotFoundError, isServerError } from '../index.ts'; // ============================================================================ // Example 1: Basic Error Handling // ============================================================================ async function basicErrorHandling() { console.log('=== Basic Error Handling ==='); const client = new HanaQueryClient({ baseUrl: 'http://localhost:3001' }); try { // This will succeed if API is available const health = await client.getHealth(); console.log('āœ… Health check successful:', health.data.status); } catch (error: unknown) { // Handle any errors if (error instanceof HanaQueryClientError) { console.error('āŒ API Error:', { type: error.type, message: error instanceof Error ? error.message : String(error), statusCode: error.statusCode }); } else { console.error('āŒ Unexpected Error:', error); } } } // ============================================================================ // Example 2: Specific Error Type Handling // ============================================================================ async function specificErrorHandling() { console.log('\\n=== Specific Error Type Handling ==='); const client = new HanaQueryClient({ baseUrl: 'http://invalid-url-that-does-not-exist.com', // This will cause network errors timeout: 1000 // Very short timeout to trigger timeout errors }); try { console.log('Attempting request with invalid URL...'); await client.getHealth(); } catch (error: unknown) { if (error instanceof NetworkError) { console.log('🌐 Network Error detected:'); console.log(` Message: ${error instanceof Error ? error.message : String(error)}`); console.log(` Original error: ${error.originalError?.message}`); } else if (error instanceof TimeoutError) { console.log('ā±ļø Timeout Error detected:'); console.log(` Message: ${error instanceof Error ? error.message : String(error)}`); console.log(` Duration: ${error.context?.duration}ms`); } else if (error instanceof ValidationError) { console.log('āœļø Validation Error detected:'); console.log(` Message: ${error instanceof Error ? error.message : String(error)}`); } else if (error instanceof AuthorizationError) { console.log('šŸ”’ Authorization Error detected:'); console.log(` Message: ${error instanceof Error ? error.message : String(error)}`); console.log(` Status Code: ${error.statusCode}`); } else if (error instanceof NotFoundError) { console.log('šŸ” Not Found Error detected:'); console.log(` Message: ${error instanceof Error ? error.message : String(error)}`); console.log(` Resource: ${error.context?.url}`); } else if (error instanceof ServerError) { console.log('šŸ”„ Server Error detected:'); console.log(` Message: ${error instanceof Error ? error.message : String(error)}`); console.log(` Status Code: ${error.statusCode}`); console.log(` Problem Details:`, error.problemDetails); } else { console.log('ā“ Unknown Error:'); console.log(` Type: ${error && typeof error === 'object' && 'constructor' in error ? error.constructor.name : 'Unknown'}`); console.log(` Message: ${error instanceof Error ? error.message : String(error)}`); } } } // ============================================================================ // Example 3: Error Type Guards // ============================================================================ async function errorTypeGuards() { console.log('\\n=== Error Type Guards ==='); const client = new HanaQueryClient({ baseUrl: 'http://localhost:99999', // Non-existent port timeout: 2000 }); try { await client.getPlans(); } catch (error: unknown) { console.log('Error caught, analyzing with type guards:'); if (isNetworkError(error)) { console.log('🌐 Type guard confirmed: Network Error'); console.log(` Can retry: ${isRetryableError(error)}`); } else if (isTimeoutError(error)) { console.log('ā±ļø Type guard confirmed: Timeout Error'); console.log(` Can retry: ${isRetryableError(error)}`); } else if (isValidationError(error)) { console.log('āœļø Type guard confirmed: Validation Error'); console.log(` Can retry: ${isRetryableError(error)}`); } else if (isNotFoundError(error)) { console.log('šŸ” Type guard confirmed: Not Found Error'); console.log(` Can retry: ${isRetryableError(error)}`); } else if (isServerError(error)) { console.log('šŸ”„ Type guard confirmed: Server Error'); console.log(` Can retry: ${isRetryableError(error)}`); } else { console.log('ā“ Type guard: Unknown error type'); } } } // ============================================================================ // Example 4: Graceful Error Handling for Expected Scenarios // ============================================================================ async function gracefulErrorHandling() { console.log('\\n=== Graceful Error Handling ==='); const client = new HanaQueryClient({ baseUrl: 'http://localhost:3001' }); // Function to safely get plan work orders (which may not exist) async function safeGetPlanWorkOrders(planId: number | string) { try { const workOrders = await client.getPlanWorkOrders(planId); return { success: true, data: workOrders.data.workOrders, count: workOrders.metadata.count }; } catch (error: unknown) { if (isNotFoundError(error)) { // This is expected for plans without work orders return { success: true, data: [], count: 0, note: 'No work orders found for this plan' }; } else { // Unexpected error return { success: false, error: error instanceof Error ? error.message : String(error) }; } } } try { // Get plans first const plans = await client.getPlans(); const firstPlanId = plans.data.plans[0]?.plan_id; if (firstPlanId) { console.log(`Checking work orders for plan ${firstPlanId}...`); const result = await safeGetPlanWorkOrders(firstPlanId); if (result.success) { console.log(`āœ… Found ${result.count} work orders`); if (result.note) { console.log(` Note: ${result.note}`); } } else { console.log(`āŒ Failed to get work orders: ${result.error}`); } } } catch (error: unknown) { console.error('Error in graceful handling example:', error instanceof Error ? error.message : String(error)); } } // ============================================================================ // Example 5: Custom Retry Logic // ============================================================================ async function customRetryLogic() { console.log('\\n=== Custom Retry Logic ==='); const client = new HanaQueryClient({ baseUrl: 'http://localhost:3001', retries: 0 // Disable built-in retries for this example }); async function retryableOperation<T>( operation: () => Promise<T>, maxRetries: number = 3, baseDelay: number = 1000 ): Promise<T> { let lastError: HanaQueryClientError | null = null; for (let attempt = 1; attempt <= maxRetries + 1; attempt++) { try { console.log(` Attempt ${attempt}/${maxRetries + 1}`); const result = await operation(); if (attempt > 1) { console.log(` āœ… Success on attempt ${attempt}`); } return result; } catch (error: unknown) { if (error instanceof HanaQueryClientError) { lastError = error; console.log(` āŒ Attempt ${attempt} failed: ${error.type} - ${error instanceof Error ? error.message : String(error)}`); // Check if error is retryable if (!isRetryableError(error)) { console.log(` 🚫 Error is not retryable, stopping`); break; } // Don't wait after the last attempt if (attempt <= maxRetries) { const delay = getRetryDelay(attempt, baseDelay, 10000); console.log(` ā³ Waiting ${delay}ms before retry...`); await new Promise(resolve => setTimeout(resolve, delay)); } } else { throw error; // Re-throw unexpected errors } } } throw lastError!; } try { console.log('Testing custom retry logic with network error...'); // This will fail but demonstrate retry logic const invalidClient = new HanaQueryClient({ baseUrl: 'http://localhost:99999', retries: 0 }); await retryableOperation( () => invalidClient.getHealth(), 3, // 3 retries 500 // 500ms base delay ); } catch (error: unknown) { console.log(`Final error after all retries: ${error instanceof Error ? error.message : String(error)}`); } } // ============================================================================ // Example 6: Error Context and Debugging // ============================================================================ async function errorContextAndDebugging() { console.log('\\n=== Error Context and Debugging ==='); const client = new HanaQueryClient({ baseUrl: 'http://localhost:3001', enableLogging: true, logLevel: 'debug' }); // Function to log detailed error information function logErrorDetails(error: HanaQueryClientError) { console.log('šŸ” Detailed Error Analysis:'); console.log(` Type: ${error.type}`); console.log(` Message: ${error instanceof Error ? error.message : String(error)}`); console.log(` Status Code: ${error.statusCode || 'N/A'}`); if (error.context) { console.log(' Request Context:'); console.log(` Method: ${error.context.method}`); console.log(` URL: ${error.context.url}`); console.log(` Duration: ${error.context.duration || 'N/A'}ms`); console.log(` Attempt: ${error.context.attempt || 'N/A'}/${error.context.maxAttempts || 'N/A'}`); } if (error.problemDetails) { console.log(' Problem Details:'); console.log(` Title: ${error.problemDetails.title}`); console.log(` Detail: ${error.problemDetails.detail}`); console.log(` Instance: ${error.problemDetails.instance}`); if (error.problemDetails.issues.length > 0) { console.log(` Issues: ${error.problemDetails.issues.join(', ')}`); } } if (error.originalError) { console.log(` Original Error: ${error.originalError.message}`); } // Error serialization for logging console.log(' JSON Representation:', JSON.stringify(error.toJSON(), null, 2)); } try { // Test with invalid parameters to trigger validation error await client.getSales({ from: 'invalid-date', to: 'also-invalid' } as any); } catch (error: unknown) { if (error instanceof HanaQueryClientError) { logErrorDetails(error); } } } // ============================================================================ // Example 7: Production-Ready Error Handling Strategy // ============================================================================ class RobustAPIService { private client: HanaQueryClient; private config: { baseUrl: string; enableLogging?: boolean; logLevel?: 'debug' | 'info' | 'warn' | 'error'; timeout?: number; retries?: number; }; constructor(config: { baseUrl: string; enableLogging?: boolean; logLevel?: 'debug' | 'info' | 'warn' | 'error'; timeout?: number; retries?: number; }) { this.config = config; this.client = new HanaQueryClient({ baseUrl: config.baseUrl, enableLogging: config.enableLogging ?? false, logLevel: config.logLevel ?? 'error', timeout: config.timeout ?? 30000, retries: config.retries ?? 3 }); } /** * Wrapper method that handles errors consistently */ private async safeApiCall<T>( operation: () => Promise<T>, fallbackValue?: T ): Promise<{ success: true; data: T } | { success: false; error: string; type: string }> { try { const data = await operation(); return { success: true, data }; } catch (error: unknown) { if (error instanceof HanaQueryClientError) { // Log error details if logging is enabled if (this.config.enableLogging) { console.error('API Error Details:', error.toJSON()); } return { success: false, error: this.getUserFriendlyMessage(error), type: error.type }; } else { // Unexpected error console.error('Unexpected API error:', error); return { success: false, error: 'An unexpected error occurred. Please try again.', type: 'unknown_error' }; } } } /** * Convert technical errors to user-friendly messages */ private getUserFriendlyMessage(error: HanaQueryClientError): string { switch (error.type) { case 'network_error': return 'Unable to connect to the server. Please check your internet connection and try again.'; case 'timeout_error': return 'The request is taking longer than expected. Please try again.'; case 'validation_error': return 'Invalid input provided. Please check your data and try again.'; case 'authorization_error': return 'You are not authorized to access this resource.'; case 'not_found_error': return 'The requested resource was not found.'; case 'server_error': return 'A server error occurred. Please try again later.'; default: return 'An error occurred while processing your request.'; } } /** * Public methods with consistent error handling */ async getHealthStatus() { return this.safeApiCall(() => this.client.getHealth()); } async getProductionPlans() { return this.safeApiCall(() => this.client.getPlans(), { data: { plans: [] }, metadata: { count: 0 } }); } async getPlanDetails(planId: number) { return this.safeApiCall(() => this.client.getPlan(planId)); } async getInventoryStatus() { return this.safeApiCall(() => this.client.getItems()); } } async function productionReadyErrorHandling() { console.log('\\n=== Production-Ready Error Handling ==='); const apiService = new RobustAPIService({ baseUrl: 'http://localhost:3001', enableLogging: true, logLevel: 'info' }); // Test the service methods console.log('Testing robust API service...'); const healthResult = await apiService.getHealthStatus(); if (healthResult.success) { console.log('āœ… Health check successful:', healthResult.data.data.status); } else { console.log('āŒ Health check failed:', healthResult.error); } const plansResult = await apiService.getProductionPlans(); if (plansResult.success) { console.log(`āœ… Found ${plansResult.data.metadata.count} production plans`); } else { console.log('āŒ Failed to get plans:', plansResult.error); console.log('Error type:', plansResult.type); } // Test with invalid plan ID const invalidPlanResult = await apiService.getPlanDetails(99999); if (invalidPlanResult.success) { console.log('āœ… Plan details retrieved'); } else { console.log('āŒ Plan not found:', invalidPlanResult.error); } } // ============================================================================ // Main Execution // ============================================================================ async function runErrorHandlingExamples() { console.log('šŸ›”ļø HANA Query API Client - Error Handling Examples'); console.log('='.repeat(60)); try { await basicErrorHandling(); await specificErrorHandling(); await errorTypeGuards(); await gracefulErrorHandling(); await customRetryLogic(); await errorContextAndDebugging(); await productionReadyErrorHandling(); console.log('\\nāœ… All error handling examples completed!'); } catch (error: unknown) { console.error('\\nāŒ Error handling examples failed:', error); } } // Run examples if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { runErrorHandlingExamples().catch(console.error); } export { basicErrorHandling, specificErrorHandling, errorTypeGuards, gracefulErrorHandling, customRetryLogic, errorContextAndDebugging, productionReadyErrorHandling, RobustAPIService, runErrorHandlingExamples };