SOLUTION: Challenge 1 - Custom Error Hierarchy ================================================ Challenge: Create an error base class and 3 subclasses (ValidationError, NetworkError, AuthError). Write a function that catches each type and handles differently. --- SOLUTION: // Base error class abstract class AppError extends Error { abstract readonly code: string; constructor(message: string) { super(message); this.name = this.constructor.name; // Restore prototype chain for instanceof to work Object.setPrototypeOf(this, AppError.prototype); } abstract getDetails(): string; } // Validation error class ValidationError extends AppError { readonly code = "VALIDATION_ERROR"; constructor( public field: string, message: string, public value?: unknown ) { super(message); Object.setPrototypeOf(this, ValidationError.prototype); } getDetails(): string { return `Field: ${this.field}, Value: ${JSON.stringify(this.value)}, Message: ${this.message}`; } } // Network error class NetworkError extends AppError { readonly code = "NETWORK_ERROR"; constructor( public statusCode: number, message: string, public url?: string ) { super(message); Object.setPrototypeOf(this, NetworkError.prototype); } getDetails(): string { return `Status: ${this.statusCode}, URL: ${this.url}, Message: ${this.message}`; } } // Authentication error class AuthError extends AppError { readonly code = "AUTH_ERROR"; constructor( public reason: "invalid_token" | "expired" | "forbidden", message: string ) { super(message); Object.setPrototypeOf(this, AuthError.prototype); } getDetails(): string { return `Reason: ${this.reason}, Message: ${this.message}`; } } --- // Error handler with type discrimination function handleError(error: unknown): void { // Type guard if (!(error instanceof Error)) { console.error("Non-Error thrown:", error); return; } // Handle each error type if (error instanceof ValidationError) { console.error("❌ Validation failed"); console.error(` Field: ${error.field}`); console.error(` Message: ${error.message}`); console.error(` Details: ${error.getDetails()}`); // Could also: send user-friendly message to UI } else if (error instanceof NetworkError) { console.error("❌ Network error"); console.error(` Status: ${error.statusCode}`); console.error(` URL: ${error.url}`); console.error(` Details: ${error.getDetails()}`); // Could also: retry with backoff } else if (error instanceof AuthError) { console.error("❌ Authentication failed"); console.error(` Reason: ${error.reason}`); console.error(` Details: ${error.getDetails()}`); // Could also: redirect to login } else if (error instanceof AppError) { console.error("❌ Application error"); console.error(` Code: ${error.code}`); console.error(` Details: ${error.getDetails()}`); } else { // Standard Error console.error("❌ Unexpected error"); console.error(` Message: ${error.message}`); console.error(` Stack: ${error.stack}`); } } --- // Test cases console.log("=== Testing Error Hierarchy ===\n"); // Test 1: Validation error try { throw new ValidationError("email", "Invalid email format", "not-an-email"); } catch (error) { handleError(error); } console.log("\n"); // Test 2: Network error try { throw new NetworkError(404, "User not found", "https://api.example.com/users/999"); } catch (error) { handleError(error); } console.log("\n"); // Test 3: Auth error try { throw new AuthError("expired", "Session token has expired"); } catch (error) { handleError(error); } console.log("\n"); // Test 4: Non-Error thrown try { throw "Something went wrong"; } catch (error) { handleError(error); } --- EXPLANATION: ERROR HIERARCHY: AppError (abstract base) ├── ValidationError ├── NetworkError └── AuthError Benefits: 1. All errors inherit from AppError 2. Each error has specific properties (field, statusCode, reason) 3. Each implements getDetails() for detailed logging 4. Can catch AppError to handle all app errors at once INSTANCEOF CHECKS: Order matters! Check specific errors first, then more general ones: if (error instanceof ValidationError) { ... } else if (error instanceof NetworkError) { ... } else if (error instanceof AuthError) { ... } else if (error instanceof AppError) { ... } else { ... } PROTOTYPE CHAIN: Object.setPrototypeOf(this, ValidationError.prototype); Necessary for instanceof to work correctly after transpilation. Some TypeScript configs need this for proper error inheritance. --- ERROR PROPERTIES: ValidationError: - field: which field failed - value: what value was invalid - message: why it failed NetworkError: - statusCode: HTTP status (404, 500, etc.) - url: which endpoint failed - message: error description AuthError: - reason: specific auth failure type - message: error description GETDETAILS(): Each error implements getDetails() for structured logging. Prevents string concatenation errors, makes logging consistent. --- REAL-WORLD USAGE: // In API handler try { await validateUser(data); await authenticateUser(token); await callBackendAPI(); } catch (error) { handleError(error); // One handler for all error types res.status(getStatusCode(error)).json(formatErrorResponse(error)); } --- ADVANCED: Status Code Mapping function getStatusCode(error: AppError): number { if (error instanceof ValidationError) return 400; if (error instanceof AuthError) return 401; if (error instanceof NetworkError) return error.statusCode; return 500; } function formatErrorResponse(error: AppError) { return { code: error.code, message: error.message, details: error.getDetails() }; } This pattern enables consistent error responses across your API.