Files
harbor-net/src/shared/errors.ts
T
dokril 116686a138
Build and Deploy Gateway / build-and-push (push) Successful in 26s
Build and Deploy Gateway / deploy (push) Successful in 14s
Implement Harbor gateway device ecosystem support
2026-08-31 02:06:22 +03:00

62 lines
4.3 KiB
TypeScript

interface ErrorDefinition {
status: number;
message: string;
retryable: boolean;
}
export const ERROR_DEFINITIONS = Object.freeze({
CONTROL_UNREACHABLE: { status: 503, message: 'Harbor сейчас недоступен.', retryable: true },
REQUEST_INVALID: { status: 400, message: 'Запрос содержит некорректные данные.', retryable: false },
ENDPOINT_NOT_FOUND: { status: 404, message: 'Запрошенный API-метод не найден.', retryable: false },
SUBSCRIPTION_INVALID: { status: 400, message: 'Ссылка подписки недействительна.', retryable: false },
SUBSCRIPTION_EXPIRED: { status: 400, message: 'Срок действия подписки истёк.', retryable: false },
SUBSCRIPTION_TRAFFIC_EXHAUSTED: { status: 400, message: 'Трафик по подписке закончился.', retryable: false },
SUBSCRIPTION_DISABLED: { status: 400, message: 'Подписка отключена провайдером.', retryable: false },
SUBSCRIPTION_REJECTED: { status: 400, message: 'Провайдер отклонил подписку.', retryable: false },
PROVIDER_UNAVAILABLE: { status: 502, message: 'Провайдер подписки временно недоступен.', retryable: true },
PROFILE_NOT_FOUND: { status: 404, message: 'Подписка больше недоступна.', retryable: false },
PROFILE_NAME_CONFLICT: { status: 409, message: 'Подписка с таким именем уже существует.', retryable: false },
PROFILE_IN_USE: { status: 409, message: 'Сначала переключите или остановите активную подписку.', retryable: false },
STATE_CONFLICT: { status: 409, message: 'Данные изменились во время операции.', retryable: true },
SERVER_NOT_FOUND: { status: 404, message: 'Выбранный сервер больше недоступен.', retryable: false },
DEVICE_NOT_FOUND: { status: 404, message: 'Устройство больше недоступно.', retryable: false },
DEVICE_TAG_NOT_FOUND: { status: 404, message: 'Тег больше недоступен.', retryable: false },
DEVICE_TAG_NAME_CONFLICT: { status: 409, message: 'Тег с таким именем уже существует.', retryable: false },
DEVICE_IDENTITY_AMBIGUOUS: { status: 409, message: 'Gateway не может безопасно применить маршрут к этому устройству.', retryable: true },
DEVICE_POLICY_APPLY_FAILED: { status: 503, message: 'Не удалось применить маршрут устройства.', retryable: true },
DIAGNOSTICS_FAILED: { status: 503, message: 'Не удалось проверить маршруты. Попробуйте ещё раз.', retryable: true },
CONFIG_INVALID: { status: 422, message: 'Конфигурация VPN недействительна.', retryable: false },
PROCESS_START_FAILED: { status: 503, message: 'Не удалось запустить VPN-процесс.', retryable: true },
OPERATION_IN_PROGRESS: { status: 409, message: 'Другая операция ещё выполняется.', retryable: true },
UNKNOWN: { status: 500, message: 'Не удалось выполнить действие.', retryable: false },
} satisfies Record<string, ErrorDefinition>);
export type HarborErrorCode = keyof typeof ERROR_DEFINITIONS;
export function errorDefinition(code: unknown): ErrorDefinition {
return typeof code === 'string' && Object.hasOwn(ERROR_DEFINITIONS, code)
? ERROR_DEFINITIONS[code as HarborErrorCode]
: ERROR_DEFINITIONS.UNKNOWN;
}
export class HarborError extends Error {
code: HarborErrorCode;
status: number;
retryable: boolean;
details: unknown;
constructor(code: string, { cause, details }: { cause?: unknown; details?: unknown } = {}) {
const definition = errorDefinition(code);
super(definition.message, { cause });
this.name = 'HarborError';
this.code = Object.hasOwn(ERROR_DEFINITIONS, code) ? code as HarborErrorCode : 'UNKNOWN';
this.status = definition.status;
this.retryable = definition.retryable;
this.details = details;
}
}
export function normalizeHarborError(error: unknown) {
return error instanceof HarborError ? error : new HarborError('UNKNOWN', { cause: error });
}