diff --git a/src/elexam_core/envelope.py b/src/elexam_core/envelope.py new file mode 100644 index 0000000..f0ed8b8 --- /dev/null +++ b/src/elexam_core/envelope.py @@ -0,0 +1,125 @@ +from datetime import datetime, timezone +from ulid import ULID +from typing import Any, Optional + +from elexam_core.context import trace_id_context + + +def success( + data: Optional[Any] = None, + message: str = "OK", + event_type: Optional[str] = None, + metadata: Optional[Any] = None, +) -> dict[str, Any]: + """ + Формирует стандартный envelope успешного ответа или события со статусом ``"success"``. + + Используется во всех эндпоинтах и обработчиках событий, когда операция завершилась + без ошибок. Поле ``errors`` всегда равно ``None``, поле ``data`` содержит результат. + + :param data: Полезная нагрузка ответа/события — объект, список или любое + сериализуемое значение, которое получит клиент. ``None``, если ответ + не предполагает тела (например, DELETE без содержимого). + :param message: Человекочитаемое сообщение о результате операции. + По умолчанию ``"OK"``. + :param event_type: Имя типа события для RabbitMQ-сообщений (например, + ``"exam.published"``). Оборачивается в структуру + ``{"id": , "name": event_type}``. ``None`` для обычных HTTP-ответов. + :param metadata: Дополнительные метаданные, не входящие в основную нагрузку + (например, пагинация, версия снимка). Кладутся в поле ``meta``. + :return: Словарь-envelope с полями ``status``, ``data``, ``message``, + ``trace_id``, ``errors``, ``meta``, ``event_type``, ``processed_at``. + """ + + return { + "status": "success", + "data": data, + "message": message, + "trace_id": trace_id_context.get(), + "errors": None, + "meta": metadata, + "event_type": {"id": str(ULID()), "name": event_type}, + "processed_at": datetime.now(timezone.utc).isoformat(), + } + +def error( + message: str = "ERROR", + errors: Optional[str] = None, + event_type: Optional[str] = None, +) -> dict[str, Any]: + """ + Формирует стандартный envelope ответа об ошибке со статусом ``"error"``. + + Используется во всех обработчиках исключений и в местах, где операция завершилась + неуспешно. Поле ``data`` всегда равно ``None``, поле ``meta`` всегда равно ``None``. + + :param message: Человекочитаемое описание ошибки, предназначенное для отображения + клиенту или логирования. По умолчанию ``"ERROR"``. + :param errors: Детали ошибки — строка с кодом из каталога ошибок, список + валидационных нарушений или иное машиночитаемое описание причины сбоя. + ``None``, если дополнительная детализация не требуется. + :param event_type: Имя типа события для RabbitMQ-сообщений (например, + ``"exam.failed"``). Оборачивается в структуру + ``{"id": , "name": event_type}``. ``None`` для обычных HTTP-ответов. + :return: Словарь-envelope с полями ``status``, ``data``, ``message``, + ``trace_id``, ``errors``, ``meta``, ``event_type``, ``processed_at``. + """ + + return { + "status": "error", + "data": None, + "message": message, + "trace_id": trace_id_context.get(), + "errors": errors, + "meta": None, + "event_type": {"id": str(ULID()), "name": event_type}, + "processed_at": datetime.now(timezone.utc).isoformat(), + } + +def custom_info( + status: str, + data: Optional[Any] = None, + message: Optional[str] = "ERROR", + errors: Optional[Any] = None, + metadata: Optional[Any] = None, + event_type: Optional[str] = None, +) -> dict[str, Any]: + """ + Формирует envelope с произвольным статусом, переданным вызывающим кодом. + + .. warning:: + Использовать эту функцию **не рекомендуется**. Применять её следует только + в крайних случаях, когда стандартные ``success()`` и ``error()`` семантически + не подходят (нестандартный протокол статусов). В подавляющем большинстве + случаев предпочитайте ``success()`` или ``error()``. + + :param status: Произвольная строка-статус, которая будет помещена в поле + ``status`` envelope. Контракт значения полностью на ответственности + вызывающего кода. + :param data: Полезная нагрузка ответа/события — объект, список или любое + сериализуемое значение, которое получит клиент. ``None``, если ответ + не предполагает тела. + :param message: Человекочитаемое сообщение о результате операции. + По умолчанию ``"ERROR"``. + :param errors: Детали ошибки — строка с кодом из каталога ошибок, список + валидационных нарушений или иное машиночитаемое описание причины сбоя. + ``None``, если дополнительная детализация не требуется. + :param metadata: Дополнительные метаданные, не входящие в основную нагрузку. + Кладутся в поле ``meta``. + :param event_type: Имя типа события для RabbitMQ-сообщений. Оборачивается + в структуру ``{"id": , "name": event_type}``. ``None`` для + обычных HTTP-ответов. + :return: Словарь-envelope с полями ``status``, ``data``, ``message``, + ``trace_id``, ``errors``, ``meta``, ``event_type``, ``processed_at``. + """ + + return { + "status": status, + "data": data, + "message": message, + "trace_id": trace_id_context.get(), + "errors": errors, + "meta": metadata, + "event_type": {"id": str(ULID()), "name": event_type}, + "processed_at": datetime.now(timezone.utc).isoformat(), + } \ No newline at end of file