feat: enhance API error handling and response structure; introduce ApiException for V2 error codes
This commit is contained in:
@@ -8,7 +8,7 @@ class ApiResponse {
|
||||
this.errors = const <String, dynamic>{},
|
||||
});
|
||||
|
||||
/// The response code (e.g., '200', '404', or custom error code).
|
||||
/// The response code (e.g., '200', '404', or V2 error code like 'VALIDATION_ERROR').
|
||||
final String code;
|
||||
|
||||
/// A descriptive message about the response.
|
||||
@@ -19,4 +19,13 @@ class ApiResponse {
|
||||
|
||||
/// A map of field-specific error messages, if any.
|
||||
final Map<String, dynamic> errors;
|
||||
|
||||
/// Whether the response indicates success (HTTP 2xx).
|
||||
bool get isSuccess {
|
||||
final int? statusCode = int.tryParse(code);
|
||||
return statusCode != null && statusCode >= 200 && statusCode < 300;
|
||||
}
|
||||
|
||||
/// Whether the response indicates failure.
|
||||
bool get isFailure => !isSuccess;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '../../../exceptions/app_exception.dart';
|
||||
import 'api_response.dart';
|
||||
import 'base_api_service.dart';
|
||||
|
||||
@@ -14,10 +15,13 @@ abstract class BaseCoreService {
|
||||
|
||||
/// Standardized wrapper to execute API actions.
|
||||
///
|
||||
/// This handles generic error normalization for unexpected non-HTTP errors.
|
||||
/// Rethrows [AppException] subclasses (domain errors) directly.
|
||||
/// Wraps unexpected non-HTTP errors into an error [ApiResponse].
|
||||
Future<ApiResponse> action(Future<ApiResponse> Function() execution) async {
|
||||
try {
|
||||
return await execution();
|
||||
} on AppException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
return ApiResponse(
|
||||
code: 'CORE_INTERNAL_ERROR',
|
||||
|
||||
@@ -286,6 +286,54 @@ class NoActiveShiftException extends ShiftException {
|
||||
// NETWORK/GENERIC EXCEPTIONS
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// API EXCEPTIONS (mapped from V2 error envelope codes)
|
||||
// ============================================================
|
||||
|
||||
/// Thrown when the V2 API returns a non-success response.
|
||||
///
|
||||
/// Carries the full error envelope so callers can inspect details.
|
||||
class ApiException extends AppException {
|
||||
/// Creates an [ApiException].
|
||||
const ApiException({
|
||||
required String apiCode,
|
||||
required String apiMessage,
|
||||
this.statusCode,
|
||||
this.details,
|
||||
super.technicalMessage,
|
||||
}) : super(code: apiCode);
|
||||
|
||||
/// The HTTP status code (e.g. 400, 404, 500).
|
||||
final int? statusCode;
|
||||
|
||||
/// The V2 API error code string (e.g. 'VALIDATION_ERROR').
|
||||
String get apiCode => code;
|
||||
|
||||
/// Optional details from the error envelope.
|
||||
final dynamic details;
|
||||
|
||||
@override
|
||||
String get messageKey {
|
||||
switch (code) {
|
||||
case 'VALIDATION_ERROR':
|
||||
return 'errors.generic.validation_error';
|
||||
case 'NOT_FOUND':
|
||||
return 'errors.generic.not_found';
|
||||
case 'FORBIDDEN':
|
||||
return 'errors.generic.forbidden';
|
||||
case 'UNAUTHENTICATED':
|
||||
return 'errors.auth.not_authenticated';
|
||||
case 'CONFLICT':
|
||||
return 'errors.generic.conflict';
|
||||
default:
|
||||
if (statusCode != null && statusCode! >= 500) {
|
||||
return 'errors.generic.server_error';
|
||||
}
|
||||
return 'errors.generic.unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Thrown when there is no network connection.
|
||||
class NetworkException extends AppException {
|
||||
const NetworkException({super.technicalMessage})
|
||||
|
||||
Reference in New Issue
Block a user