feat(infra): preserve status code and network flag in ApiException

Replace the untyped Exception(msg) thrown by mapDioError with an
ApiException that exposes statusCode and isNetworkError alongside the
message. Callers can now classify failures by HTTP status without
string-matching on the error message — this unblocks typed error
mapping in the auth feature modules.
This commit is contained in:
2026-04-17 11:10:13 +02:00
parent 72d44b81df
commit eff6f01924

View File

@@ -1,7 +1,23 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
class ApiException implements Exception {
final int? statusCode;
final String message;
final bool isNetworkError;
const ApiException({
required this.message,
this.statusCode,
this.isNetworkError = false,
});
@override
String toString() => 'ApiException($statusCode): $message';
}
Future<T> safeCall<T>(Future<T> Function() call, String fallbackMsg) async {
try {
return await call();
@@ -10,10 +26,23 @@ Future<T> safeCall<T>(Future<T> Function() call, String fallbackMsg) async {
}
}
Exception mapDioError(DioException error, {required String defaultMessage}) {
ApiException mapDioError(DioException error, {required String defaultMessage}) {
final statusCode = error.response?.statusCode;
final apiMsg = _extractApiMessage(error.response?.data);
final msg = apiMsg ?? error.message ?? defaultMessage;
return Exception(msg);
final isNetwork =
error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.sendTimeout ||
error.type == DioExceptionType.receiveTimeout ||
error.type == DioExceptionType.connectionError ||
(error.error is SocketException);
return ApiException(
message: msg,
statusCode: statusCode,
isNetworkError: isNetwork,
);
}
String? _extractApiMessage(Object? data) {
@@ -46,6 +75,7 @@ String? _extractApiMessage(Object? data) {
}
String formatErrorMessage(Object error) {
if (error is ApiException) return error.message;
final raw = error.toString();
if (raw.startsWith('Exception: ')) {
return raw.substring('Exception: '.length);