safe_call.dart 2.13 KB
import 'dart:convert';

import 'package:dio/dio.dart';
import 'package:get/get.dart';

import '../constants/network_const.dart';
import '../error/app_error.dart';
import '../error/app_error_handler.dart';
import '../error/http_error_handling_policy.dart';
import '../error/http_server_custom_error.dart';
import 'app_result.dart';

/// Wraps async work into [AppResult] with optional global error handling.
///
/// Pass a non-null [errorHandlingPolicy] to enable global side effects (toast,
/// logout). Pass null (or omit) for silent mode — caller handles errors itself.
Future<AppResult<T>> safeCall<T>({
  required Future<T> Function() call,
  HttpErrorHandlingPolicy? errorHandlingPolicy =
      HttpErrorHandlingPolicy.defaultPolicy,
}) async {
  try {
    final data = await call();
    return AppSuccess(data);
  } on DioException catch (e) {
    if (e.type == DioExceptionType.cancel) {
      return const AppFailure(AppCancelledError());
    }
    final error = _mapDioException(e);
    if (errorHandlingPolicy != null) {
      Get.find<AppErrorHandler>().handle(error, policy: errorHandlingPolicy);
    }
    return AppFailure(error);
  } on AppCancelledError catch (e) {
    return AppFailure(e);
  } catch (e) {
    final error = AppUnknownError(e);
    if (errorHandlingPolicy != null) {
      Get.find<AppErrorHandler>().handle(error, policy: errorHandlingPolicy);
    }
    return AppFailure(error);
  }
}

AppError _mapDioException(DioException e) {
  final response = e.response;
  if (response == null) {
    return AppNetworkError(e);
  }

  HttpServerCustomError? serverError;
  final body = response.data;
  if (body is Map<String, dynamic>) {
    serverError = HttpServerCustomError.fromJson(body);
  } else if (body is String && body.isNotEmpty) {
    try {
      final map = jsonDecode(body) as Map<String, dynamic>;
      serverError = HttpServerCustomError.fromJson(map);
    } catch (_) {
      serverError = null;
    }
  }

  final token = response.requestOptions.headers[NetworkConst.headerAccessToken]?.toString();

  return AppHttpError(
    statusCode: response.statusCode,
    customError: serverError,
    accessToken: token,
    cause: e,
  );
}