refactor: improve Dio error parsing and add user-friendly fallback messages for network and status code failures
This commit is contained in:
@@ -231,16 +231,19 @@ class ApiClient {
|
||||
/// Wrap a DioException with parsed error data.
|
||||
DioException _wrapError(DioException error) {
|
||||
final response = error.response;
|
||||
|
||||
// 1. Try to parse a structured error from the JSON response body
|
||||
if (response != null) {
|
||||
final data = response.data;
|
||||
if (data is Map<String, dynamic>) {
|
||||
final message = data['message'] is List
|
||||
? (data['message'] as List).join(', ')
|
||||
: data['message']?.toString() ?? 'An error occurred';
|
||||
final rawMessage = data['message'] ?? data['error'];
|
||||
final message = rawMessage is List
|
||||
? (rawMessage).join(', ')
|
||||
: rawMessage?.toString();
|
||||
|
||||
if (message != null && message.isNotEmpty) {
|
||||
final errors = (data['errors'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => FieldError.fromJson(e as Map<String, dynamic>))
|
||||
?.map((e) => FieldError.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[];
|
||||
|
||||
@@ -255,16 +258,67 @@ class ApiClient {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback — build a user-friendly message from the status code or
|
||||
// Dio exception type. NEVER use `error.message` because Dio emits a
|
||||
// giant technical blob ("This exception was thrown because the
|
||||
// response has a status code of 401 and RequestOptions.validateStatus
|
||||
// was configured to throw...") which is useless to end users.
|
||||
return DioException(
|
||||
requestOptions: error.requestOptions,
|
||||
response: error.response,
|
||||
error: ApiException(
|
||||
message: error.message ?? 'Network error occurred',
|
||||
message: _friendlyMessageFor(error),
|
||||
statusCode: response?.statusCode,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _friendlyMessageFor(DioException error) {
|
||||
// Network-level failures take priority over status codes
|
||||
switch (error.type) {
|
||||
case DioExceptionType.connectionTimeout:
|
||||
case DioExceptionType.sendTimeout:
|
||||
case DioExceptionType.receiveTimeout:
|
||||
return 'The request timed out. Please check your connection and try again.';
|
||||
case DioExceptionType.connectionError:
|
||||
return 'Unable to reach the server. Please check your internet connection.';
|
||||
case DioExceptionType.cancel:
|
||||
return 'Request was cancelled.';
|
||||
case DioExceptionType.badCertificate:
|
||||
return 'Secure connection failed. Please try again later.';
|
||||
case DioExceptionType.badResponse:
|
||||
case DioExceptionType.unknown:
|
||||
break;
|
||||
}
|
||||
|
||||
// Status-code-based fallback
|
||||
final code = error.response?.statusCode;
|
||||
switch (code) {
|
||||
case 400:
|
||||
return 'The request was invalid. Please check your input.';
|
||||
case 401:
|
||||
return 'Invalid email or password.';
|
||||
case 403:
|
||||
return 'You don\'t have permission to perform this action.';
|
||||
case 404:
|
||||
return 'The requested resource was not found.';
|
||||
case 409:
|
||||
return 'This action conflicts with existing data.';
|
||||
case 422:
|
||||
return 'Some fields are invalid. Please check your input.';
|
||||
case 429:
|
||||
return 'Too many attempts. Please wait a moment and try again.';
|
||||
case 500:
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
return 'Server is temporarily unavailable. Please try again shortly.';
|
||||
}
|
||||
|
||||
return 'Something went wrong. Please try again.';
|
||||
}
|
||||
}
|
||||
|
||||
class _QueuedRequest {
|
||||
|
||||
Reference in New Issue
Block a user