Skip to content

ErrorParser

import: @azure-net/kit

Основная задача парсера ошибок - привести все ошибки в приложении на @azure-net/kit к единому виду. Также его внутри себя используют createAsyncAction и createAsyncResource, при их создании вы можете кастомизировать парсер и передать свой кастомный парсер в createAsyncAction и createAsyncResource.

Глобально на уровне приложения создается три вида ошибок

  • HttpServiceError
  • SchemaFail
  • Error (базовый js-ный)

Все эти ошибки проходя через парсер приходят к единому виду AppError

typescript
export type AppErrorType = 'http' | 'app' | 'schema';

// http означает что ошибка произошла на уровне запроса, скорее всего в репозитории, так как HttpServiceError рождается там.
// app означает что где то конкретно вы выбросили исключение через throw Error(), хотя и поймать в приложении такое реально, но сложно
// schema означает что ошибка появилась на уровне валидации в схеме, а следовательно валидация просто не пройдена

export interface AppError<T = unknown, CustomErrorField = never> {
    type: AppErrorType; // тип ошибки
    message: string; // сообщение
    fields?: RequestErrors<T>; // поля валидации если ошибка появилась в схеме, или их кастомизировали и добавили вы
    status?: number; // статус если это ошибка http, при schema 422, при app статус нет, если вы его не добавили
    original?: HttpServiceError<T> | SchemaFail<T> | Error; // Оригинал ошибки
    custom?: CustomErrorField; // Кастомное поле, изначально отсутствует, но вы можете добавить туда свои данные.
}

Пример создания парсера

typescript
import { createErrorParser, createAsyncHelpers } from '@azure-net/kit';
import { createPresenterFactory } from '@azure-net/kit/edges';

export const ErrorParser = createErrorParser();

// Глобально предполагается что вы встраиваете этот парсер в AsyncActions и ваш базовый presenter.
// После этого все ваши запросы с ошибками будут приведены к единому виду, а также вы сможете единым образом парсить ошибки в презентере
export const AsyncHelpers = createAsyncHelpers({ parseError: ErrorParser });

export const AppPresenter = createPresenterFactory({ ...AsyncHelpers, parseError: ErrorParser });

Пример кастомизации парсера

Парсер принимает в себя объект из трех ключей

  • parseBaseError
  • parseHttpError
  • parseSchemaError

Все эти методы обязаны вернуть AppError чтобы парсер с ними работал.

typescript
import { createPresenterFactory } from '@azure-net/kit/edges';
import { EnvironmentUtil } from '@azure-net/kit/tools';
import { notificationManager } from '$widgets';
import { createAsyncHelpers, createErrorParser, type AppError, type SchemaFail, type RequestErrors } from '@azure-net/kit';
import { type HttpServiceError } from '@azure-net/kit/infra';

// parseBaseError - пример кастомизации
export const parseBaseError = <D = never>(error: Error): AppError<never, D> => {
    if (EnvironmentUtil.isBrowser) {
        const { alert } = notificationManager();
        alert('shared.somethingWentWrong');
    }
    return {
        type: 'app',
        message: error.message,
        original: error
    };
};

// parseSchemaError - пример кастомизации, вы вольны изменять ключи или добавлять кастомные
export const parseSchemaError = <SchemaData = unknown, D = never>(error: SchemaFail<SchemaData>): AppError<SchemaData, D> => {
    return {
        type: 'schema',
        message: 'schema validation error',
        status: 422,
        fields: error.getErrors(),
        original: error
    };
};

// parseHttpError - пример кастомизации с учетом что с бэка тоже летит валидация, которую мы например захэндлить не можем
// Бэкендер как обычно шлет валидацию массивом и мы единожды настраиваем парсер и больше его ошибки по 100 раз не распечатываем
const parseHttpError = <T = unknown, D = never>(error: HttpServiceError<T>): AppError<T, D> => {
    const notFatalErrors = [400, 422, 401];
    if (!notFatalErrors.includes(error.status) && EnvironmentUtil.isBrowser) {
        const { alert } = notificationManager();
        alert('shared.somethingWentWrong');
    }
    const parseValidationErrors = (): RequestErrors<T> => {
        const fields: RequestErrors<T> = {};
        const entries: [string, string[]][] = Object.entries((error?.data as { data: Record<keyof T, string[]> })?.data ?? {});
        entries.forEach(([key, value]) => {
            fields[key as keyof typeof fields] = (Array.isArray(value) ? value[0] : value) as (typeof fields)[keyof typeof fields];
        });
        return fields;
    };
    return {
        type: 'http',
        message: error.message ?? 'unexpected error',
        status: error.status ?? 500,
        original: error,
        fields: notFatalErrors.includes(error.status) ? parseValidationErrors() : undefined
    };
};

// Дальше кормим все в createErrorParser, AsyncHelpers и Presenter

export const ErrorHandler = createErrorParser({ parseBaseError, parseHttpError, parseSchemaError });
export const AsyncHelpers = createAsyncHelpers({ parseError: ErrorHandler });

export const AppPresenter = createPresenterFactory({ ...AsyncHelpers, handleError: ErrorHandler });