# Стандартизация успешных ответов и ошибок в Metabot

### Общий подход к обработке результатов между модулями, API и бизнес-логикой

В Metabot разные части системы постоянно обмениваются результатами выполнения операций:

* JS-плагины вызывают другие плагины;
* low-code сценарии запускают кастомный код;
* API-эндпоинты принимают запросы извне;
* внутренние модули возвращают данные друг другу.

Если каждый компонент будет возвращать ответы **в своём формате**, система очень быстро превращается в хаос:

* где-то `true / false`,
* где-то `throw error`,
* где-то `{ status: "ok" }`,
* где-то просто строка.

Чтобы этого избежать, в Metabot используется **единый стандарт ответов** — плагин
`Common.Utils.Response`.

---

## Зачем вообще стандартизировать ответы

Стандартизация решает сразу несколько задач:

* 📦 единый формат для **успешных результатов и ошибок**;
* 🔗 предсказуемое взаимодействие между модулями;
* 🧠 упрощение логики: не нужно каждый раз гадать, что вернёт функция;
* 🌐 удобный проброс результатов во внешние API;
* 🧪 возможность централизованно логировать и анализировать ошибки.

Ключевая идея простая:

> **Любая функция либо возвращает success-response, либо error-response.
> Исключения — редкость, а не норма.**

---

## Где используется `Common.Utils.Response`

Плагин применяется практически везде:

* в JS-плагинах (`Common.Helpers.*`, `Common.CJM.*`, AI-модули);
* во внутренних API-эндпоинтах;
* в бизнес-логике;
* в интеграциях с внешними системами;
* в helper-функциях, которые могут вызываться цепочкой.

Если вы пишете свой модуль — **используйте этот подход**, либо создайте свой аналог по той же схеме.

---

## Базовая идея формата ответа

Ответ всегда — это объект.

### Успешный результат

```js
{
  success: true,
  ...data
}
```

### Ошибка

```js
{
  success: false,
  error: true,
  message: "Описание ошибки",
  error_code: "OPTIONAL_CODE"
}
```

Это минимальный контракт, который:

* легко проверять (`if (!result.success)`);
* удобно сериализовать;
* безопасно передавать между слоями системы.

---

### Подключение плагина `Common.Utils.Response`

Перед использованием стандартных функций для возврата успешных ответов и ошибок плагин необходимо подключить в вашем JS-модуле.

Пример подключения:

```js
// Подключаем Response плагин для обработки ответов
const { getErrorResponse, getSuccessResponse } = require('Common.Utils.Response');
```

После подключения функции `getSuccessResponse` и `getErrorResponse` доступны для использования в:

* helper-плагинах;
* бизнес-логике;
* API-эндпоинтах;
* кастомных скриптах внутри Metabot.

📌 Плагин `Common.Utils.Response` является общим и доступен на всех серверах Metabot, поэтому дополнительной установки не требуется.

---

## Основные функции плагина

Плагин `Common.Utils.Response` предоставляет набор helper-функций.

### `getSuccessResponse()`

Используется при успешном выполнении операции.

Примеры использования:

```js
return getSuccessResponse("Операция выполнена успешно")
```

или

```js
return getSuccessResponse({
  message: "Скрипт создан",
  script_id: 12345
})
```

---

### `getErrorResponse()`

Используется для возврата ошибки **без выбрасывания исключения**.

```js
return getErrorResponse("Не удалось отправить сообщение")
```

или

```js
return getErrorResponse(
  { message: "Ошибка валидации", field: "email" },
  "VALIDATION_ERROR"
)
```

Почему это важно:
ошибка становится **данными**, а не неконтролируемым исключением.

---

### Дополнительные типы ответов

Плагин также содержит готовые шаблоны для типовых ситуаций:

* `getValidationErrorResponse()` — ошибки валидации;
* `getNotFoundResponse()` — сущность не найдена;
* `getForbiddenResponse()` — доступ запрещён.

Их удобно использовать в API и сервисных методах, чтобы не изобретать форматы заново.

---

## Почему мы не кидаем exceptions везде

В классическом backend-подходе ошибки часто бросаются через `throw`.
В Metabot это **не основной сценарий**, по нескольким причинам:

1. JS-код выполняется внутри движка (V8), а не в отдельном Node-процессе.
2. Исключения могут:

   * прервать выполнение сценария;
   * привести к неочевидным последствиям;
   * быть плохо читаемыми в логах.
3. Большинство ошибок — **ожидаемые**:

   * не найден скрипт,
   * пустой параметр,
   * недоступен внешний сервис,
   * неверный формат данных.

Поэтому:

* **ошибки возвращаются как результат**;
* `throw` используется только для действительно критических ситуаций.

---

## Как это работает в цепочках вызовов

Типичный сценарий:

```js
const result = sendFormattedMessage(text, 'HTML')

if (!result.success) {
  // можно залогировать
  // можно показать fallback
  // можно вернуть ошибку выше
  return result
}

// продолжаем логику
```

Таким образом:

* каждый уровень решает, что делать с ошибкой;
* ошибка может «подняться» наверх без искажения;
* формат ответа остаётся единым на всём пути.

---

## Использование в API-эндпоинтах

Внутренние и внешние API Metabot обычно выглядят так:

```js
try {
  // бизнес-логика
  return getSuccessResponse({ result })
} catch (e) {
  return getErrorResponse(e.message)
}
```

Снаружи клиент **всегда** получает предсказуемый JSON, независимо от источника ошибки.

Это критично для:

* фронтендов;
* интеграций;
* автоматических сценариев;
* AI-агентов, которые анализируют ответы.

---

## Исходный код плагина Common.Utils.Response

```js
/**
 * 📦 Плагин: Common.Response.Wrapper
 * Автор: @ArtemGarashko
 * Версия: 1.4
 * Дата последнего обновления: 26 апр 2025
 *
 * Назначение:
 * - Формирование ответов об успешных и неудачных операциях.
 * - Используется в API, между компонентами, в бизнес-логике.
 */

function getErrorResponse(errorOrData, errorCode = null) {
  const result = { success: false, error: true }

  if (typeof errorOrData === "string") {
    result.message = errorOrData
  } else if (typeof errorOrData === "object") {
    Object.assign(result, errorOrData)
  }

  if (errorCode) {
    result.error_code = errorCode
  }

  return result
}

function getSuccessResponse(dataOrMessage = {}) {
  const result = { success: true }

  if (typeof dataOrMessage === "string") {
    result.message = dataOrMessage
  } else if (typeof dataOrMessage === "object") {
    Object.assign(result, dataOrMessage)
  }

  return result
}

function getValidationErrorResponse(details) {
  return {
    success: false,
    error: true,
    message: details.message || "Validation failed",
    fields: details.fields || []
  }
}

function getNotFoundResponse(entity = "Entity") {
  return {
    success: false,
    error: true,
    message: `${entity} not found`
  }
}

function getForbiddenResponse(reason = "Access denied") {
  return {
    success: false,
    error: true,
    message: reason
  }
}

exports.getErrorResponse = getErrorResponse
exports.getSuccessResponse = getSuccessResponse
exports.getValidationErrorResponse = getValidationErrorResponse
exports.getNotFoundResponse = getNotFoundResponse
exports.getForbiddenResponse = getForbiddenResponse
```
> ⚠️ Версия `Common.Utils.Response`, приведённая в статье, может отличаться от версии, установленной на сервере Metabot.
> Используйте код как ориентир и архитектурный шаблон.

---

## Связь с предыдущим уроком

В уроке про [форматированные сообщения](formatirovannye-soobshheniya-v-telegram-v-metabot) мы использовали этот подход: 

* плагин `sendFormattedMessage` **отправляет сообщение**;
* но **возвращает стандартизированный результат**;
* вызывающая сторона может:

  * проигнорировать результат;
  * обработать ошибку;
  * использовать данные дальше.

То есть:

> helper-плагины делают действие
> response-плагин описывает результат

Это разделение ответственности — важный архитектурный принцип.

---

## Можно ли сделать свой стандарт

Да, конечно.

`Common.Utils.Response` — это:

* рекомендованный стандарт;
* доступный на всех серверах Metabot;
* проверенный в бою.

Но если вам нужен:

* другой формат;
* дополнительные поля;
* специфичная структура под внешний API —

вы можете:

* скопировать подход;
* реализовать свой response-helper;
* использовать его внутри своего продукта.

Главное — **консистентность**.

---

## Итог

В этом уроке мы зафиксировали ключевую практику Metabot:

* ошибки — это данные, а не исключения;
* успешные результаты и ошибки имеют единый формат;
* плагины и модули легко комбинируются;
* API и внутренняя логика говорят на одном языке.

Если при проектировании нового модуля у тебя возникает мысль
«а может тут просто вернуть true?» —
это хороший момент остановиться и проверить:
**а не ломаешь ли ты этим общую архитектурную договорённость**.

Именно такие мелкие решения потом определяют, будет система масштабируемой или нет.