feat: surface read-only load errors
This commit is contained in:
55
lib/app/error_message.dart
Normal file
55
lib/app/error_message.dart
Normal file
@@ -0,0 +1,55 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
String describeLoadError(Object error) {
|
||||
if (error is DioException) {
|
||||
final statusCode = error.response?.statusCode;
|
||||
|
||||
if (statusCode == 401 || statusCode == 403) {
|
||||
return 'Нет доступа: API key отклонён ($statusCode).';
|
||||
}
|
||||
|
||||
if (_isNetworkFailure(error)) {
|
||||
return 'Backend недоступен: ${error.message ?? error.type.name}.';
|
||||
}
|
||||
|
||||
final responseMessage = _responseMessage(error);
|
||||
if (responseMessage != null) {
|
||||
return 'Backend вернул ошибку $statusCode: $responseMessage';
|
||||
}
|
||||
|
||||
if (statusCode != null) {
|
||||
return 'Backend вернул ошибку $statusCode.';
|
||||
}
|
||||
|
||||
return 'Ошибка запроса: ${error.message ?? error.type.name}.';
|
||||
}
|
||||
|
||||
if (error is FormatException) {
|
||||
return 'Неожиданный ответ backend: ${error.message}';
|
||||
}
|
||||
|
||||
return error.toString();
|
||||
}
|
||||
|
||||
bool _isNetworkFailure(DioException error) {
|
||||
return switch (error.type) {
|
||||
DioExceptionType.connectionTimeout ||
|
||||
DioExceptionType.sendTimeout ||
|
||||
DioExceptionType.receiveTimeout ||
|
||||
DioExceptionType.connectionError ||
|
||||
DioExceptionType.unknown => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
String? _responseMessage(DioException error) {
|
||||
final data = error.response?.data;
|
||||
if (data is Map) {
|
||||
final value = data['detail'] ?? data['message'] ?? data['error'];
|
||||
return value?.toString();
|
||||
}
|
||||
if (data is String && data.trim().isNotEmpty) {
|
||||
return data.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
34
lib/app/load_state.dart
Normal file
34
lib/app/load_state.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
enum LoadStatus { idle, loading, data, empty, error }
|
||||
|
||||
class LoadState<T> {
|
||||
final LoadStatus status;
|
||||
final T data;
|
||||
final String? errorMessage;
|
||||
|
||||
const LoadState.idle(this.data)
|
||||
: status = LoadStatus.idle,
|
||||
errorMessage = null;
|
||||
|
||||
const LoadState.loading(this.data)
|
||||
: status = LoadStatus.loading,
|
||||
errorMessage = null;
|
||||
|
||||
const LoadState.data(this.data)
|
||||
: status = LoadStatus.data,
|
||||
errorMessage = null;
|
||||
|
||||
const LoadState.empty(this.data)
|
||||
: status = LoadStatus.empty,
|
||||
errorMessage = null;
|
||||
|
||||
const LoadState.error(this.data, this.errorMessage)
|
||||
: status = LoadStatus.error;
|
||||
|
||||
bool get isIdle => status == LoadStatus.idle;
|
||||
|
||||
bool get isLoading => status == LoadStatus.loading;
|
||||
|
||||
bool get hasError => status == LoadStatus.error;
|
||||
|
||||
bool get isEmpty => status == LoadStatus.empty;
|
||||
}
|
||||
Reference in New Issue
Block a user