@fab33/fab-errors
Version:
Modern error handling library with typed contexts, Error.cause chains, and ErrorSpecs
136 lines • 8.16 kB
JavaScript
/**
* @file src/chain-utils.ts
* @description Утилиты для работы с цепочками ошибок (Error.cause).
* @version 1.0.8
* @date 2025-05-30
* @updated Добавлена пустая строка в конце файла (eol-last).
*
* HISTORY:
* v1.0.8 (2025-05-30): Исправлено правило eol-last.
* v1.0.7 (2025-05-30): Изменен ErrorCriteria.type на `new (...args: any[]) => Error`. Добавлено явное присвоение confirmedError: Error.
* v1.0.6 (2025-05-30): Исправления ESLint (no-unused-vars, no-explicit-any, space-before-function-paren), eol-last. (Версия из XML)
* v1.0.5 (2025-05-30): Использовано явное утверждение типа `as Error` для `currentErrorForLevel` в `checkErrorChain`.
* v1.0.4 (2025-05-30): Убрана вложенная проверка `currentErrorForLevel instanceof Error` в блоке проверки типа.
* v1.0.3 (2025-05-29): Добавлена явная проверка `instanceof Error` для currentErrorForLevel в блоке проверки типа.
* v1.0.2 (2025-05-29): Реструктуризация проверки типа в checkErrorChain для исправления TS2339.
* v1.0.1 (2025-05-29): Исправлена ошибка типов TS2339 в checkErrorChain (первая попытка).
* v1.0.0 (2025-05-29): Начальная реализация hasErrorInChain и checkErrorChain.
*/
/**
* Проверяет, присутствует ли в цепочке ошибок (начиная с `error` и далее по `error.cause`)
* ошибка, соответствующая указанным критериям.
*
* @param error Начальная ошибка для проверки.
* @param criteria Объект с критериями поиска (`code`, `type`, `message`).
* @returns `true`, если ошибка, соответствующая критериям, найдена в цепочке, иначе `false`.
* @deterministic Да, для той же ошибки и критериев.
*/
export function hasErrorInChain(error, criteria) {
let currentError = error;
while (currentError) {
let codeMatch = true;
if (criteria.code !== undefined) {
const errorWithCode = currentError;
codeMatch = typeof errorWithCode.code === 'string' && errorWithCode.code === criteria.code;
}
let typeMatch = true;
if (criteria.type !== undefined) {
typeMatch = currentError instanceof criteria.type;
}
let messageMatch = true;
if (criteria.message !== undefined) {
if (typeof currentError.message === 'string') {
const currentMessageLower = currentError.message.toLowerCase();
const expectedMessages = Array.isArray(criteria.message) ? criteria.message : [criteria.message];
messageMatch = expectedMessages.every(msg => typeof msg === 'string' && currentMessageLower.includes(msg.toLowerCase()));
}
else {
messageMatch = false;
}
}
if (codeMatch && typeMatch && messageMatch) {
return true;
}
const cause = currentError.cause;
if (cause instanceof Error) {
currentError = cause;
}
else {
currentError = undefined;
}
}
return false;
}
/**
* Проверяет, соответствует ли цепочка ошибок (начиная с `error` и далее по `error.cause`)
* ожидаемой структуре, описанной в `expectedChain`.
*
* @param error Начальная ошибка для проверки.
* @param expectedChain Массив объектов `ExpectedChainLevel`, описывающих каждый ожидаемый уровень цепочки.
* @returns `true`, если цепочка полностью соответствует ожиданиям.
* @throws `Error` если цепочка не соответствует ожиданиям (сообщение содержит детали несоответствия).
* @deterministic Да, для той же ошибки и ожидаемой цепочки.
*/
export function checkErrorChain(error, expectedChain) {
let currentErrorInChain = error;
let levelIndex = 0;
if (!Array.isArray(expectedChain)) {
throw new Error('checkErrorChain: expectedChain must be an array.');
}
for (levelIndex = 0; levelIndex < expectedChain.length; levelIndex++) {
const expectedLevel = expectedChain[levelIndex];
const errorPrefix = `Error chain mismatch at level ${levelIndex}`;
const errorForThisIteration = currentErrorInChain;
if (!errorForThisIteration) {
throw new Error(`${errorPrefix}: Expected level with code '${expectedLevel.code || 'any'}' but error chain ended.`);
}
// На этом этапе TypeScript должен знать, что errorForThisIteration это Error.
// 1. Проверка кода ошибки (если указан)
if (expectedLevel.code !== undefined) {
const errorWithCode = errorForThisIteration;
if (typeof errorWithCode.code !== 'string' || errorWithCode.code !== expectedLevel.code) {
throw new Error(`${errorPrefix}: Expected code '${expectedLevel.code}', got '${String(errorWithCode.code) || 'undefined'}'. Message: "${errorForThisIteration.message}"`);
}
}
// 2. Проверка типа ошибки (если указан)
if (expectedLevel.type !== undefined) {
if (!(errorForThisIteration instanceof expectedLevel.type)) {
// Явно присваиваем переменной с типом Error, чтобы помочь TS, если он "теряет" сужение.
const confirmedError = errorForThisIteration;
const actualTypeName = confirmedError.constructor.name;
const actualMessage = confirmedError.message;
throw new Error(`${errorPrefix}: Expected type '${expectedLevel.type.name}', got '${actualTypeName}'. Message: "${actualMessage}"`);
}
}
// 3. Проверка содержания сообщения (если указано)
if (expectedLevel.message !== undefined) {
// errorForThisIteration здесь также должен быть Error
const errorWithMessage = errorForThisIteration;
if (typeof errorWithMessage.message !== 'string') {
throw new Error(`${errorPrefix}: Error message is not a string or missing for code '${String(errorWithMessage.code) || 'unknown'}'.`);
}
const currentMessageLower = errorWithMessage.message.toLowerCase();
const expectedMessages = Array.isArray(expectedLevel.message) ? expectedLevel.message : [expectedLevel.message];
for (const expectedMsg of expectedMessages) {
if (typeof expectedMsg !== 'string' || !currentMessageLower.includes(expectedMsg.toLowerCase())) {
throw new Error(`${errorPrefix}: Message does not contain expected text '${expectedMsg}'. Full message: "${errorWithMessage.message}"`);
}
}
}
const cause = errorForThisIteration.cause;
if (cause instanceof Error) {
currentErrorInChain = cause;
}
else {
currentErrorInChain = undefined;
}
}
if (currentErrorInChain) {
const nextError = currentErrorInChain;
const nextErrorCode = nextError.code;
throw new Error(`Error chain validation failed: Error chain has more levels than expected (${expectedChain.length}). Next level error: ${nextError.constructor.name} [${String(nextErrorCode) || 'unknown'}] "${nextError.message}"`);
}
return true;
}
// END OF: src/chain-utils.ts
//# sourceMappingURL=chain-utils.js.map