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.
|
/// Wrap a DioException with parsed error data.
|
||||||
DioException _wrapError(DioException error) {
|
DioException _wrapError(DioException error) {
|
||||||
final response = error.response;
|
final response = error.response;
|
||||||
|
|
||||||
|
// 1. Try to parse a structured error from the JSON response body
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
final data = response.data;
|
final data = response.data;
|
||||||
if (data is Map<String, dynamic>) {
|
if (data is Map<String, dynamic>) {
|
||||||
final message = data['message'] is List
|
final rawMessage = data['message'] ?? data['error'];
|
||||||
? (data['message'] as List).join(', ')
|
final message = rawMessage is List
|
||||||
: data['message']?.toString() ?? 'An error occurred';
|
? (rawMessage).join(', ')
|
||||||
|
: rawMessage?.toString();
|
||||||
|
|
||||||
|
if (message != null && message.isNotEmpty) {
|
||||||
final errors = (data['errors'] as List<dynamic>?)
|
final errors = (data['errors'] as List<dynamic>?)
|
||||||
?.map(
|
?.map((e) => FieldError.fromJson(e as Map<String, dynamic>))
|
||||||
(e) => FieldError.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList() ??
|
.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(
|
return DioException(
|
||||||
requestOptions: error.requestOptions,
|
requestOptions: error.requestOptions,
|
||||||
response: error.response,
|
response: error.response,
|
||||||
error: ApiException(
|
error: ApiException(
|
||||||
message: error.message ?? 'Network error occurred',
|
message: _friendlyMessageFor(error),
|
||||||
statusCode: response?.statusCode,
|
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 {
|
class _QueuedRequest {
|
||||||
|
|||||||
Reference in New Issue
Block a user