httpRequest function
Envía una petición HTTP a un endpoint de la API.
path: ruta relativa (ej: '/services/api/...')data: body; puede ser Map<String,dynamic>, FormData, MultipartFile, List, etc.method: 'GET'|'POST'|'PUT'|'DELETE' (default POST)
Retorna response.data del backend o lanza ApacuanaAPIError.
Implementation
Future<dynamic> httpRequest(
String path, {
dynamic data,
String method = 'POST',
}) async {
final dio = _dio;
if (dio == null) {
throw StateError(
'Apacuana SDK: Cliente HTTP no inicializado. Llama a apacuana.init() primero.',
);
}
// Si el caller ya pasó un FormData, lo clonamos para evitar reutilizar streams finalizados
dynamic bodyToSend = data is FormData ? _cloneFormData(data) : data;
if (data is! FormData) {
// Si es Map y contiene MultipartFile u otras estructuras, convertimos a FormData
if (data is Map<String, dynamic>) {
final containsMultipart = data.values.any((v) =>
v is MultipartFile ||
(v is List && v.any((e) => e is MultipartFile)) ||
(v is Map && v.values.any((e) => e is MultipartFile)));
if (containsMultipart) {
final formData = FormData();
data.forEach((key, value) {
if (value is MultipartFile) {
// Clonar para evitar usar el mismo stream en múltiples requests
formData.files.add(MapEntry(key, value.clone()));
} else if (value is List) {
for (final item in value) {
if (item is MultipartFile) {
formData.files.add(MapEntry(key, item.clone()));
} else {
formData.fields.add(MapEntry(key, item?.toString() ?? ''));
}
}
} else if (value is Map) {
// Serializamos mapas anidados simples a JSON string
formData.fields.add(MapEntry(key, value.toString()));
} else {
formData.fields.add(MapEntry(key, value?.toString() ?? ''));
}
});
bodyToSend = formData;
} else {
// keep as Map
bodyToSend = data;
}
} else {
// data es otro tipo (String, List, etc) -> se pasa tal cual
bodyToSend = data;
}
} // else: data is FormData -> bodyToSend already cloned arriba
try {
Response res;
switch (method.toUpperCase()) {
case 'GET':
// Para GET, si bodyToSend es Map lo pasamos como queryParameters.
if (bodyToSend is Map<String, dynamic>) {
res = await dio.get(path, queryParameters: bodyToSend);
} else {
res = await dio.get(path);
}
break;
case 'POST':
res = await dio.post(path, data: bodyToSend);
break;
case 'PUT':
res = await dio.put(path, data: bodyToSend);
break;
case 'DELETE':
res = await dio.delete(path, data: bodyToSend);
break;
default:
throw ApacuanaAPIError(
'Método HTTP no soportado: $method',
statusCode: 405,
errorCode: 'UNSUPPORTED_HTTP_METHOD',
);
}
return res.data;
} on DioException catch (e) {
// Ya mapeamos en interceptores a ApacuanaAPIError cuando es posible.
final underlying = e.error;
if (underlying is ApacuanaAPIError) {
// ignore: avoid_print
print('[HTTP Client] Fallo en la petición a $path: $underlying');
throw underlying;
}
// Fallback si por alguna razón no pasó por el interceptor.
if (e.response != null) {
final status = e.response!.statusCode ?? 0;
final data = e.response!.data;
String message = 'Error desconocido desde la API.';
String code = 'API_ERROR';
if (data is Map) {
message = (data['msg'] ?? data['message'] ?? message).toString();
code = (data['code'] ?? code).toString();
}
final apiErr =
ApacuanaAPIError(message, statusCode: status, errorCode: code);
// ignore: avoid_print
print('[HTTP Client] Fallo en la petición a $path: $apiErr');
throw apiErr;
}
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.sendTimeout ||
e.type == DioExceptionType.receiveTimeout ||
e.type == DioExceptionType.connectionError) {
final apiErr = ApacuanaAPIError(
'Error de conexión a la red, timeout o CORS.',
statusCode: 0,
errorCode: 'NETWORK_ERROR',
);
// ignore: avoid_print
print('[HTTP Client] Fallo en la petición a $path: $apiErr');
throw apiErr;
}
final apiErr = ApacuanaAPIError(
'Error desconocido en la petición: ${e.message}',
statusCode: 0,
errorCode: 'UNKNOWN_REQUEST_ERROR',
);
// ignore: avoid_print
print('[HTTP Client] Fallo en la petición a $path: $apiErr');
throw apiErr;
}
}