refactor(apps): 重构数据层目录结构并新增启动预热编排器
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'api_exception.dart';
|
||||
import 'api_interceptor.dart';
|
||||
import 'i_api_client.dart';
|
||||
import '../storage/token_storage.dart';
|
||||
|
||||
class ApiClient implements IApiClient {
|
||||
final Dio _dio;
|
||||
final TokenStorage _tokenStorage;
|
||||
final ApiInterceptor _interceptor;
|
||||
|
||||
factory ApiClient({
|
||||
required String baseUrl,
|
||||
required TokenStorage tokenStorage,
|
||||
Dio? dio,
|
||||
}) {
|
||||
final effectiveDio =
|
||||
dio ??
|
||||
Dio(
|
||||
BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
connectTimeout: const Duration(seconds: 10),
|
||||
receiveTimeout: const Duration(seconds: 20),
|
||||
sendTimeout: const Duration(seconds: 20),
|
||||
),
|
||||
);
|
||||
final interceptor = ApiInterceptor(
|
||||
tokenStorage: tokenStorage,
|
||||
dio: effectiveDio,
|
||||
);
|
||||
effectiveDio.interceptors.add(interceptor);
|
||||
return ApiClient._(
|
||||
dio: effectiveDio,
|
||||
tokenStorage: tokenStorage,
|
||||
interceptor: interceptor,
|
||||
);
|
||||
}
|
||||
|
||||
ApiClient._({
|
||||
required Dio dio,
|
||||
required TokenStorage tokenStorage,
|
||||
required ApiInterceptor interceptor,
|
||||
}) : _dio = dio,
|
||||
_tokenStorage = tokenStorage,
|
||||
_interceptor = interceptor;
|
||||
|
||||
Dio get dio => _dio;
|
||||
|
||||
void resetInterceptor() {
|
||||
_interceptor.reset();
|
||||
}
|
||||
|
||||
void setRefreshCallback(Future<bool> Function(String) refresh) {
|
||||
_interceptor.onTokenRefresh = () async {
|
||||
final token = await _tokenStorage.getRefreshToken();
|
||||
if (token == null) return false;
|
||||
return refresh(token);
|
||||
};
|
||||
}
|
||||
|
||||
void setAuthFailureCallback(Future<void> Function() onAuthFailure) {
|
||||
_interceptor.onAuthFailure = onAuthFailure;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> get<T>(String path, {Options? options}) async {
|
||||
try {
|
||||
return await _dio.get<T>(path, options: options);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> post<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Options? options,
|
||||
}) async {
|
||||
try {
|
||||
return await _dio.post<T>(path, data: data, options: options);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> patch<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Options? options,
|
||||
}) async {
|
||||
try {
|
||||
return await _dio.patch<T>(path, data: data, options: options);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> put<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Options? options,
|
||||
}) async {
|
||||
try {
|
||||
return await _dio.put<T>(path, data: data, options: options);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response<T>> delete<T>(
|
||||
String path, {
|
||||
dynamic data,
|
||||
Options? options,
|
||||
}) async {
|
||||
try {
|
||||
return await _dio.delete<T>(path, data: data, options: options);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Stream<String>> getSseLines(
|
||||
String path, {
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.get<ResponseBody>(
|
||||
path,
|
||||
options: Options(responseType: ResponseType.stream, headers: headers),
|
||||
);
|
||||
final responseBody = response.data;
|
||||
if (responseBody == null) {
|
||||
return const Stream<String>.empty();
|
||||
}
|
||||
return responseBody.stream
|
||||
.cast<List<int>>()
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter());
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../core/l10n/l10n.dart';
|
||||
import 'error_code_mapper.dart';
|
||||
|
||||
abstract class ApiException implements Exception {
|
||||
final String message;
|
||||
final int? statusCode;
|
||||
final String? errorCode;
|
||||
final Map<String, dynamic>? errorParams;
|
||||
|
||||
const ApiException(
|
||||
this.message, {
|
||||
this.statusCode,
|
||||
this.errorCode,
|
||||
this.errorParams,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
|
||||
factory ApiException.fromDioError(Object error) {
|
||||
if (error is ApiException) return error;
|
||||
if (error is DioException) {
|
||||
final response = error.response;
|
||||
final statusCode = response?.statusCode;
|
||||
final data = response?.data;
|
||||
|
||||
String detail;
|
||||
String? errorCode;
|
||||
Map<String, dynamic>? errorParams;
|
||||
final decodedData = _normalizeErrorData(data);
|
||||
|
||||
if (decodedData is Map<String, dynamic>) {
|
||||
detail =
|
||||
(decodedData['detail'] ??
|
||||
decodedData['message'] ??
|
||||
decodedData['error'])
|
||||
?.toString() ??
|
||||
L10n.current.errorRequestFailed;
|
||||
final code = decodedData['code'];
|
||||
if (code is String && code.trim().isNotEmpty) {
|
||||
errorCode = code;
|
||||
}
|
||||
final params = decodedData['params'];
|
||||
if (params is Map<String, dynamic>) {
|
||||
errorParams = params;
|
||||
} else if (params is Map) {
|
||||
errorParams = params.map(
|
||||
(key, value) => MapEntry(key.toString(), value),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
detail = _networkErrorMessage(error);
|
||||
}
|
||||
|
||||
final localized = _localizeError(
|
||||
detail,
|
||||
statusCode,
|
||||
errorCode: errorCode,
|
||||
errorParams: errorParams,
|
||||
);
|
||||
|
||||
if (statusCode == 401) {
|
||||
return UnauthorizedException(message: localized, errorCode: errorCode);
|
||||
}
|
||||
if (statusCode == 422) {
|
||||
return ValidationException(
|
||||
localized,
|
||||
errors: data,
|
||||
statusCode: statusCode,
|
||||
errorCode: errorCode,
|
||||
errorParams: errorParams,
|
||||
);
|
||||
}
|
||||
return ServerException(
|
||||
localized,
|
||||
statusCode: statusCode,
|
||||
errorCode: errorCode,
|
||||
errorParams: errorParams,
|
||||
);
|
||||
}
|
||||
return ServerException(L10n.current.errorNetwork);
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? _normalizeErrorData(dynamic data) {
|
||||
if (data is Map<String, dynamic>) {
|
||||
return data;
|
||||
}
|
||||
if (data is Map) {
|
||||
return data.map((key, value) => MapEntry(key.toString(), value));
|
||||
}
|
||||
if (data is String && data.trim().isNotEmpty) {
|
||||
try {
|
||||
final decoded = jsonDecode(data);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
return decoded;
|
||||
}
|
||||
if (decoded is Map) {
|
||||
return decoded.map((key, value) => MapEntry(key.toString(), value));
|
||||
}
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String _localizeError(
|
||||
String detail,
|
||||
int? statusCode, {
|
||||
String? errorCode,
|
||||
Map<String, dynamic>? errorParams,
|
||||
}) {
|
||||
final mapped = resolveErrorCodeMessage(errorCode, params: errorParams);
|
||||
if (mapped != null && mapped.isNotEmpty) {
|
||||
return mapped;
|
||||
}
|
||||
if (statusCode == 403) {
|
||||
return L10n.current.errorForbidden;
|
||||
}
|
||||
if (statusCode == 404) {
|
||||
return L10n.current.errorNotFound;
|
||||
}
|
||||
if (statusCode == 429) {
|
||||
return L10n.current.errorTooManyRequests;
|
||||
}
|
||||
if (statusCode != null && statusCode >= 500) {
|
||||
return L10n.current.errorServer;
|
||||
}
|
||||
return L10n.current.errorGenericSafe;
|
||||
}
|
||||
|
||||
static String _networkErrorMessage(DioException error) {
|
||||
if (error.type == DioExceptionType.connectionTimeout ||
|
||||
error.type == DioExceptionType.sendTimeout ||
|
||||
error.type == DioExceptionType.receiveTimeout) {
|
||||
return L10n.current.errorNetworkTimeout;
|
||||
}
|
||||
|
||||
if (error.type == DioExceptionType.connectionError ||
|
||||
error.type == DioExceptionType.unknown) {
|
||||
return L10n.current.errorNetworkUnavailable;
|
||||
}
|
||||
|
||||
return L10n.current.errorRequestFailed;
|
||||
}
|
||||
}
|
||||
|
||||
class ServerException extends ApiException {
|
||||
const ServerException(
|
||||
super.message, {
|
||||
super.statusCode,
|
||||
super.errorCode,
|
||||
super.errorParams,
|
||||
});
|
||||
}
|
||||
|
||||
class UnauthorizedException extends ApiException {
|
||||
UnauthorizedException({String? message, String? errorCode})
|
||||
: super(
|
||||
message ?? L10n.current.errorReLogin,
|
||||
statusCode: 401,
|
||||
errorCode: errorCode,
|
||||
);
|
||||
}
|
||||
|
||||
class ValidationException extends ApiException {
|
||||
final Map<String, dynamic>? errors;
|
||||
const ValidationException(
|
||||
super.message, {
|
||||
this.errors,
|
||||
super.statusCode,
|
||||
super.errorCode,
|
||||
super.errorParams,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../storage/token_storage.dart';
|
||||
|
||||
class ApiInterceptor extends Interceptor {
|
||||
final TokenStorage tokenStorage;
|
||||
final Dio dio;
|
||||
final Duration refreshFailureCooldown;
|
||||
Future<bool> Function()? onTokenRefresh;
|
||||
Future<void> Function()? onAuthFailure;
|
||||
Future<bool>? _refreshFuture;
|
||||
Future<void>? _authFailureFuture;
|
||||
DateTime? _refreshBlockedUntil;
|
||||
|
||||
static const _retriedRequestKey = '_auth_retry_once';
|
||||
static const _refreshPathSuffix = '/api/v1/auth/sessions/refresh';
|
||||
|
||||
ApiInterceptor({
|
||||
required this.tokenStorage,
|
||||
required this.dio,
|
||||
this.refreshFailureCooldown = const Duration(seconds: 5),
|
||||
this.onTokenRefresh,
|
||||
});
|
||||
|
||||
@override
|
||||
void onRequest(
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler handler,
|
||||
) async {
|
||||
final token = await tokenStorage.getAccessToken();
|
||||
if (token != null) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||
final requestOptions = err.requestOptions;
|
||||
final isUnauthorized = err.response?.statusCode == 401;
|
||||
final shouldHandleUnauthorized =
|
||||
isUnauthorized && _isAuthenticatedRequest(requestOptions);
|
||||
|
||||
if (err.response?.statusCode == 401 &&
|
||||
onTokenRefresh != null &&
|
||||
!_shouldSkipRefresh(requestOptions)) {
|
||||
final refreshed = await _refreshTokenSingleflight();
|
||||
if (refreshed) {
|
||||
final token = await tokenStorage.getAccessToken();
|
||||
if (token != null) {
|
||||
final retryHeaders = Map<String, dynamic>.from(requestOptions.headers)
|
||||
..['Authorization'] = 'Bearer $token';
|
||||
final retryExtra = Map<String, dynamic>.from(requestOptions.extra)
|
||||
..[_retriedRequestKey] = true;
|
||||
final retryOptions = requestOptions.copyWith(
|
||||
headers: retryHeaders,
|
||||
extra: retryExtra,
|
||||
);
|
||||
try {
|
||||
final response = await dio.fetch(retryOptions);
|
||||
handler.resolve(response);
|
||||
return;
|
||||
} on DioException {
|
||||
// Retry failed, proceed with original error.
|
||||
}
|
||||
}
|
||||
} else if (shouldHandleUnauthorized) {
|
||||
await _notifyAuthFailureSingleflight();
|
||||
}
|
||||
} else if (shouldHandleUnauthorized && _shouldSkipRefresh(requestOptions)) {
|
||||
await _notifyAuthFailureSingleflight();
|
||||
}
|
||||
handler.next(err);
|
||||
}
|
||||
|
||||
bool _isAuthenticatedRequest(RequestOptions options) {
|
||||
return options.headers['Authorization'] != null;
|
||||
}
|
||||
|
||||
Future<void> _notifyAuthFailureSingleflight() {
|
||||
final existing = _authFailureFuture;
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
final callback = onAuthFailure;
|
||||
if (callback == null) {
|
||||
return Future<void>.value();
|
||||
}
|
||||
|
||||
final future = callback().whenComplete(() {
|
||||
_authFailureFuture = null;
|
||||
});
|
||||
_authFailureFuture = future;
|
||||
return future;
|
||||
}
|
||||
|
||||
bool _shouldSkipRefresh(RequestOptions options) {
|
||||
final blockedUntil = _refreshBlockedUntil;
|
||||
if (blockedUntil != null && DateTime.now().isBefore(blockedUntil)) {
|
||||
return true;
|
||||
}
|
||||
return _normalizePath(options.path) == _refreshPathSuffix ||
|
||||
options.extra[_retriedRequestKey] == true;
|
||||
}
|
||||
|
||||
String _normalizePath(String rawPath) {
|
||||
final parsed = Uri.tryParse(rawPath);
|
||||
if (parsed == null) {
|
||||
return rawPath.replaceFirst(RegExp(r'/+$'), '');
|
||||
}
|
||||
final normalized = parsed.path.replaceFirst(RegExp(r'/+$'), '');
|
||||
return normalized.isEmpty ? '/' : normalized;
|
||||
}
|
||||
|
||||
Future<bool> _refreshTokenSingleflight() {
|
||||
final inflight = _refreshFuture;
|
||||
if (inflight != null) {
|
||||
return inflight;
|
||||
}
|
||||
final future = onTokenRefresh!().catchError((_) => false).whenComplete(() {
|
||||
_refreshFuture = null;
|
||||
});
|
||||
_refreshFuture = future;
|
||||
return future.then((refreshed) {
|
||||
if (refreshed) {
|
||||
_refreshBlockedUntil = null;
|
||||
} else {
|
||||
_refreshBlockedUntil = DateTime.now().add(refreshFailureCooldown);
|
||||
}
|
||||
return refreshed;
|
||||
});
|
||||
}
|
||||
|
||||
void reset() {
|
||||
_refreshFuture = null;
|
||||
_authFailureFuture = null;
|
||||
_refreshBlockedUntil = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
import '../../core/l10n/l10n.dart';
|
||||
|
||||
String? mapErrorCodeToL10nKey(
|
||||
String? errorCode, {
|
||||
Map<String, dynamic>? params,
|
||||
}) {
|
||||
if (errorCode == null || errorCode.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (errorCode) {
|
||||
case 'AGENT_RUN_INPUT_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_RUN_MESSAGES_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_INVALID_LAST_EVENT_ID':
|
||||
return 'errorAgentInvalidLastEventId';
|
||||
case 'AGENT_SSE_CONNECTION_LIMIT':
|
||||
return 'errorAgentSseConnectionLimit';
|
||||
case 'AGENT_ATTACHMENT_EMPTY':
|
||||
return 'errorAgentAttachmentEmpty';
|
||||
case 'AGENT_ATTACHMENT_TOO_LARGE':
|
||||
return 'errorAgentAttachmentTooLarge';
|
||||
case 'AGENT_AUDIO_UNSUPPORTED_FORMAT':
|
||||
return 'errorAgentAudioUnsupportedFormat';
|
||||
case 'AGENT_AUDIO_TOO_LARGE':
|
||||
return 'errorAgentAudioTooLarge';
|
||||
case 'AGENT_AUDIO_EMPTY':
|
||||
return 'errorAgentAudioEmpty';
|
||||
case 'AGENT_ASR_UNAVAILABLE':
|
||||
return 'errorAgentAsrUnavailable';
|
||||
case 'AGENT_FORBIDDEN':
|
||||
return 'errorForbidden';
|
||||
case 'AGENT_PAYLOAD_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_ATTACHMENTS_TOO_MANY':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_SIGNED_IMAGE_URL_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_ATTACHMENT_STORAGE_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'AGENT_ATTACHMENT_UNSUPPORTED_TYPE':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_ATTACHMENT_UPLOAD_FAILED':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_ATTACHMENT_BUCKET_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_ATTACHMENT_PATH_SCOPE_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_SIGNED_URL_GENERATION_FAILED':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_SESSION_ID_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'AGENT_SESSION_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'AGENT_USER_ID_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'INVALID_BINARY_URL_HOST':
|
||||
return 'errorAgentInvalidBinaryUrl';
|
||||
case 'INVALID_BINARY_URL_BUCKET':
|
||||
return 'errorAgentInvalidBinaryUrl';
|
||||
case 'INVALID_BINARY_URL_PATH_SCOPE':
|
||||
return 'errorAgentInvalidBinaryUrl';
|
||||
case 'AUTH_SERVICE_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'AUTH_TOO_MANY_REQUESTS':
|
||||
return 'errorTooManyRequests';
|
||||
case 'AUTH_VERIFICATION_CODE_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'AUTH_REFRESH_TOKEN_INVALID':
|
||||
return 'errorReLogin';
|
||||
case 'AUTH_REFRESH_TOKEN_MISSING':
|
||||
return 'errorReLogin';
|
||||
case 'AUTH_USER_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'AUTH_UNAUTHORIZED':
|
||||
return 'errorReLogin';
|
||||
case 'JWT_VERIFIER_NOT_CONFIGURED':
|
||||
return 'errorServer';
|
||||
case 'AUTOMATION_JOB_LIMIT_EXCEEDED':
|
||||
return 'errorGenericSafe';
|
||||
case 'AUTOMATION_SYSTEM_JOB_MODIFICATION_FORBIDDEN':
|
||||
return 'errorForbidden';
|
||||
case 'AUTOMATION_JOB_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'AUTOMATION_JOB_STORE_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'LOOKUP_FAILED':
|
||||
return 'errorServer';
|
||||
case 'INTERNAL_ERROR':
|
||||
return 'errorServer';
|
||||
case 'MISSING_RUNTIME_ARGS':
|
||||
return 'errorGenericSafe';
|
||||
case 'TOOL_PENDING_APPROVAL':
|
||||
return 'errorGenericSafe';
|
||||
case 'TOOL_REJECTED':
|
||||
return 'errorForbidden';
|
||||
case 'USER_STORE_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'USER_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'USER_UPDATE_FIELDS_EMPTY':
|
||||
return 'errorGenericSafe';
|
||||
case 'USER_AVATAR_UNSUPPORTED_TYPE':
|
||||
return 'errorGenericSafe';
|
||||
case 'USER_AVATAR_TOO_LARGE':
|
||||
return 'errorGenericSafe';
|
||||
case 'USER_AVATAR_EMPTY':
|
||||
return 'errorGenericSafe';
|
||||
case 'USER_AVATAR_UPLOAD_FAILED':
|
||||
return 'errorGenericSafe';
|
||||
case 'USER_AUTH_LOOKUP_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'TODO_SERVICE_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'TODO_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'TODO_ACCESS_FORBIDDEN':
|
||||
return 'errorForbidden';
|
||||
case 'TODO_REORDER_DUPLICATE_ID':
|
||||
return 'errorGenericSafe';
|
||||
case 'TODO_STATUS_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'TODO_PRIORITY_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_INVALID_TIME_RANGE':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_STORE_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'SCHEDULE_ITEM_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'SCHEDULE_ITEM_START_AT_TIMEZONE_REQUIRED':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_PAGE_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_PAGE_SIZE_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_SHARE_FORBIDDEN':
|
||||
return 'errorForbidden';
|
||||
case 'SCHEDULE_ITEM_SHARE_PERMISSION_EXCEEDED':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_SUBSCRIPTION_ALREADY_ACTIVE':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_INVITE_ALREADY_SUBSCRIBED':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_INVITE_ALREADY_PENDING':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_AUTH_LOOKUP_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'SCHEDULE_ITEM_PENDING_INVITE_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'SCHEDULE_ITEM_ACCEPT_SUBSCRIPTION_FAILED':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_REJECT_SUBSCRIPTION_FAILED':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_DATETIME_TIMEZONE_REQUIRED':
|
||||
return 'errorGenericSafe';
|
||||
case 'SCHEDULE_ITEM_DATETIME_REQUIRED':
|
||||
return 'errorGenericSafe';
|
||||
case 'INBOX_MESSAGE_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'INBOX_MESSAGE_STORE_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'MEMORIES_USER_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'MEMORIES_WORK_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'MEMORIES_SERVICE_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'FRIEND_REQUEST_SELF_NOT_ALLOWED':
|
||||
return 'errorGenericSafe';
|
||||
case 'FRIEND_ALREADY_ACCEPTED':
|
||||
return 'errorGenericSafe';
|
||||
case 'FRIEND_REQUEST_BLOCKED':
|
||||
return 'errorGenericSafe';
|
||||
case 'FRIEND_REQUEST_ALREADY_SENT':
|
||||
return 'errorGenericSafe';
|
||||
case 'FRIENDSHIP_SERVICE_UNAVAILABLE':
|
||||
return 'errorServer';
|
||||
case 'FRIEND_REQUEST_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'FRIEND_REQUEST_FORBIDDEN':
|
||||
return 'errorForbidden';
|
||||
case 'FRIEND_REQUEST_NOT_PENDING':
|
||||
return 'errorGenericSafe';
|
||||
case 'FRIEND_INBOX_MESSAGE_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'FRIENDSHIP_DATA_INVALID':
|
||||
return 'errorGenericSafe';
|
||||
case 'FRIENDSHIP_NOT_FOUND':
|
||||
return 'errorNotFound';
|
||||
case 'FRIENDSHIP_REMOVE_REQUIRES_ACCEPTED':
|
||||
return 'errorGenericSafe';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String? resolveErrorCodeMessage(
|
||||
String? errorCode, {
|
||||
Map<String, dynamic>? params,
|
||||
}) {
|
||||
final key = mapErrorCodeToL10nKey(errorCode, params: params);
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'errorAgentSseConnectionLimit':
|
||||
return L10n.current.errorAgentSseConnectionLimit;
|
||||
case 'errorAgentAttachmentEmpty':
|
||||
return L10n.current.errorAgentAttachmentEmpty;
|
||||
case 'errorAgentAttachmentTooLarge':
|
||||
return L10n.current.errorAgentAttachmentTooLarge;
|
||||
case 'errorAgentAudioEmpty':
|
||||
return L10n.current.errorAgentAudioEmpty;
|
||||
case 'errorAgentAudioTooLarge':
|
||||
return L10n.current.errorAgentAudioTooLarge;
|
||||
case 'errorAgentAudioUnsupportedFormat':
|
||||
return L10n.current.errorAgentAudioUnsupportedFormat;
|
||||
case 'errorAgentAsrUnavailable':
|
||||
return L10n.current.errorAgentAsrUnavailable;
|
||||
case 'errorAgentInvalidLastEventId':
|
||||
return L10n.current.errorAgentInvalidLastEventId;
|
||||
case 'errorAgentInvalidBinaryUrl':
|
||||
return L10n.current.errorAgentInvalidBinaryUrl;
|
||||
case 'errorForbidden':
|
||||
return L10n.current.errorForbidden;
|
||||
case 'errorNotFound':
|
||||
return L10n.current.errorNotFound;
|
||||
case 'errorTooManyRequests':
|
||||
return L10n.current.errorTooManyRequests;
|
||||
case 'errorServer':
|
||||
return L10n.current.errorServer;
|
||||
case 'errorGenericSafe':
|
||||
return L10n.current.errorGenericSafe;
|
||||
case 'errorReLogin':
|
||||
return L10n.current.errorReLogin;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
abstract class IApiClient {
|
||||
Future<Response<T>> get<T>(String path, {Options? options});
|
||||
Future<Response<T>> post<T>(String path, {dynamic data, Options? options});
|
||||
Future<Response<T>> put<T>(String path, {dynamic data, Options? options});
|
||||
Future<Response<T>> patch<T>(String path, {dynamic data, Options? options});
|
||||
Future<Response<T>> delete<T>(String path, {dynamic data, Options? options});
|
||||
Future<Stream<String>> getSseLines(
|
||||
String path, {
|
||||
Map<String, String>? headers,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user