| 1 | class AppException implements Exception { |
| 2 | final AppErrorType type; |
| 3 | final String message; |
| 4 | final String? detail; |
| 5 | final dynamic originalError; |
| 6 | |
| 7 | const AppException({ |
| 8 | required this.type, |
| 9 | required this.message, |
| 10 | this.detail, |
| 11 | this.originalError, |
| 12 | }); |
| 13 | |
| 14 | @override |
| 15 | String toString() => detail != null ? '$message: $detail' : message; |
| 16 | } |
| 17 | |
| 18 | enum AppErrorType { |
| 19 | connectionTimeout( |
| 20 | 'Connection timed out. Check your network and server address.', |
| 21 | ), |
| 22 | connectionRefused('Could not connect to server. Is it running?'), |
| 23 | authFailed('Authentication failed. Check your API key.'), |
| 24 | serverError('Server returned an error. Check server logs.'), |
| 25 | networkError('Network error. Check your internet connection.'), |
| 26 | parseError('Failed to parse server response.'), |
| 27 | modelNotFound('Model not found on server.'), |
| 28 | contextLengthExceeded( |
| 29 | 'Context length exceeded. Try shortening your messages.', |
| 30 | ), |
| 31 | rateLimited('Rate limited. Please wait before sending more messages.'), |
| 32 | storageError('Storage error. Your data may not be saved.'), |
| 33 | unknown('An unexpected error occurred.'); |
| 34 | |
| 35 | const AppErrorType(this.userMessage); |
| 36 | final String userMessage; |
| 37 | |
| 38 | static AppErrorType fromStatusCode(int statusCode) { |
| 39 | if (statusCode == 401 || statusCode == 403) return AppErrorType.authFailed; |
| 40 | if (statusCode == 404) return AppErrorType.modelNotFound; |
| 41 | if (statusCode == 429) return AppErrorType.rateLimited; |
| 42 | if (statusCode >= 500) return AppErrorType.serverError; |
| 43 | return AppErrorType.unknown; |
| 44 | } |
| 45 | |
| 46 | static AppErrorType fromException(dynamic error) { |
| 47 | final msg = error.toString().toLowerCase(); |
| 48 | if (msg.contains('connection refused') || |
| 49 | msg.contains('connection failed')) { |
| 50 | return AppErrorType.connectionRefused; |
| 51 | } |
| 52 | if (msg.contains('timeout') || msg.contains('timed out')) { |
| 53 | return AppErrorType.connectionTimeout; |
| 54 | } |
| 55 | if (msg.contains('unauthorized') || |
| 56 | msg.contains('forbidden') || |
| 57 | msg.contains('401')) { |
| 58 | return AppErrorType.authFailed; |
| 59 | } |
| 60 | if (msg.contains('429') || msg.contains('rate limit')) { |
| 61 | return AppErrorType.rateLimited; |
| 62 | } |
| 63 | if (msg.contains('network') || msg.contains('socket')) { |
| 64 | return AppErrorType.networkError; |
| 65 | } |
| 66 | if (msg.contains('formatexception') || msg.contains('parse')) { |
| 67 | return AppErrorType.parseError; |
| 68 | } |
| 69 | return AppErrorType.unknown; |
| 70 | } |
| 71 | } |