You've already forked 1cai-public
mirror of
https://github.com/DmitrL-dev/1cai-public.git
synced 2026-05-01 16:59:46 +02:00
565 lines
23 KiB
Python
565 lines
23 KiB
Python
# [NEXUS IDENTITY] ID: -7262033236925051300 | DATE: 2025-11-19
|
|
|
|
"""
|
|
Маппинг исключений между 1С и Python для 1С MCP сервера
|
|
|
|
Обеспечивает трансляцию исключений через границу API:
|
|
- Соответствие кодов ошибок между системами
|
|
- Сохранение контекста и трассировки
|
|
- Поддержка correlation_id для отслеживания
|
|
- Нормализация сообщений для跨системного использования
|
|
|
|
Основан на стандартах из проекта 1c_mcp и RFC 7807 (Problem Details).
|
|
"""
|
|
|
|
from typing import Any, Dict, Optional, Type
|
|
|
|
try:
|
|
from .base import ErrorCategory, McpError
|
|
from .integration import *
|
|
from .mcp import *
|
|
from .transport import *
|
|
from .validation import *
|
|
except ImportError:
|
|
from base import McpError, ErrorCategory
|
|
from validation import *
|
|
from transport import *
|
|
from integration import *
|
|
from mcp import *
|
|
|
|
import uuid
|
|
|
|
# Условный импорт логирования
|
|
try:
|
|
import logging
|
|
_has_logging = True
|
|
except ImportError:
|
|
_has_logging = False
|
|
class MockLogger:
|
|
def debug(self, msg, *args, **kwargs): pass
|
|
def info(self, msg, *args, **kwargs): pass
|
|
def warning(self, msg, *args, **kwargs): pass
|
|
def error(self, msg, *args, **kwargs): pass
|
|
def critical(self, msg, *args, **kwargs): pass
|
|
|
|
class MockLogging:
|
|
@staticmethod
|
|
def getLogger(name):
|
|
return MockLogger()
|
|
|
|
logging = MockLogging()
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ErrorMappingConfig:
|
|
"""Конфигурация маппинга ошибок между системами"""
|
|
|
|
# Базовые соответствия кодов ошибок 1С ↔ Python
|
|
ERROR_CODE_MAPPING = {
|
|
# Системные ошибки (E001-E019)
|
|
"E001": ("SYSTEM001", ErrorCategory.SYSTEM),
|
|
"E002": ("SYSTEM002", ErrorCategory.SYSTEM),
|
|
"E003": ("SYSTEM003", ErrorCategory.SYSTEM),
|
|
"E004": ("SYSTEM004", ErrorCategory.SYSTEM),
|
|
"E005": ("SYSTEM005", ErrorCategory.SYSTEM),
|
|
"E006": ("SYSTEM006", ErrorCategory.SYSTEM),
|
|
"E007": ("SYSTEM007", ErrorCategory.SYSTEM),
|
|
"E008": ("SYSTEM008", ErrorCategory.SYSTEM),
|
|
"E009": ("SYSTEM009", ErrorCategory.SYSTEM),
|
|
"E010": ("SYSTEM010", ErrorCategory.SYSTEM),
|
|
|
|
# Ошибки валидации (E020-E039)
|
|
"E020": ("VALIDATION001", ErrorCategory.VALIDATION),
|
|
"E021": ("VALIDATION002", ErrorCategory.VALIDATION),
|
|
"E022": ("VALIDATION003", ErrorCategory.VALIDATION),
|
|
"E023": ("VALIDATION004", ErrorCategory.VALIDATION),
|
|
"E024": ("VALIDATION005", ErrorCategory.VALIDATION),
|
|
"E025": ("VALIDATION006", ErrorCategory.VALIDATION),
|
|
"E026": ("VALIDATION007", ErrorCategory.VALIDATION),
|
|
"E027": ("VALIDATION008", ErrorCategory.VALIDATION),
|
|
"E028": ("VALIDATION009", ErrorCategory.VALIDATION),
|
|
"E029": ("VALIDATION010", ErrorCategory.VALIDATION),
|
|
|
|
# Транспортные ошибки (E040-E059)
|
|
"E040": ("TRANSPORT001", ErrorCategory.TRANSPORT),
|
|
"E041": ("TRANSPORT002", ErrorCategory.TRANSPORT),
|
|
"E042": ("TRANSPORT003", ErrorCategory.TRANSPORT),
|
|
"E043": ("TRANSPORT004", ErrorCategory.TRANSPORT),
|
|
"E044": ("TRANSPORT005", ErrorCategory.TRANSPORT),
|
|
"E045": ("TRANSPORT006", ErrorCategory.TRANSPORT),
|
|
"E046": ("TRANSPORT007", ErrorCategory.TRANSPORT),
|
|
"E047": ("TRANSPORT008", ErrorCategory.TRANSPORT),
|
|
"E048": ("TRANSPORT009", ErrorCategory.TRANSPORT),
|
|
"E049": ("TRANSPORT010", ErrorCategory.TRANSPORT),
|
|
|
|
# Интеграционные ошибки (E060-E079)
|
|
"E060": ("INTEGRATION001", ErrorCategory.INTEGRATION),
|
|
"E061": ("INTEGRATION002", ErrorCategory.INTEGRATION),
|
|
"E062": ("INTEGRATION003", ErrorCategory.INTEGRATION),
|
|
"E063": ("INTEGRATION004", ErrorCategory.INTEGRATION),
|
|
"E064": ("INTEGRATION005", ErrorCategory.INTEGRATION),
|
|
"E065": ("INTEGRATION006", ErrorCategory.INTEGRATION),
|
|
"E066": ("INTEGRATION007", ErrorCategory.INTEGRATION),
|
|
"E067": ("INTEGRATION008", ErrorCategory.INTEGRATION),
|
|
"E068": ("INTEGRATION009", ErrorCategory.INTEGRATION),
|
|
"E069": ("INTEGRATION010", ErrorCategory.INTEGRATION),
|
|
|
|
# Аутентификационные ошибки (E080-E089)
|
|
"E080": ("AUTH001", ErrorCategory.AUTH),
|
|
"E081": ("AUTH002", ErrorCategory.AUTH),
|
|
"E082": ("AUTH003", ErrorCategory.AUTH),
|
|
"E083": ("AUTH004", ErrorCategory.AUTH),
|
|
"E084": ("AUTH005", ErrorCategory.AUTH),
|
|
"E085": ("AUTH006", ErrorCategory.AUTH),
|
|
"E086": ("AUTH007", ErrorCategory.AUTH),
|
|
"E087": ("AUTH008", ErrorCategory.AUTH),
|
|
"E088": ("AUTH009", ErrorCategory.AUTH),
|
|
"E089": ("AUTH010", ErrorCategory.AUTH),
|
|
|
|
# Транзакционные ошибки (E090-E099)
|
|
"E090": ("DATABASE001", ErrorCategory.DATABASE),
|
|
"E091": ("DATABASE002", ErrorCategory.DATABASE),
|
|
"E092": ("DATABASE003", ErrorCategory.DATABASE),
|
|
"E093": ("DATABASE004", ErrorCategory.DATABASE),
|
|
"E094": ("DATABASE005", ErrorCategory.DATABASE),
|
|
"E095": ("DATABASE006", ErrorCategory.DATABASE),
|
|
"E096": ("DATABASE007", ErrorCategory.DATABASE),
|
|
"E097": ("DATABASE008", ErrorCategory.DATABASE),
|
|
"E098": ("DATABASE009", ErrorCategory.DATABASE),
|
|
"E099": ("DATABASE010", ErrorCategory.DATABASE),
|
|
}
|
|
|
|
# Соответствие классов исключений Python → 1С
|
|
PYTHON_TO_1C_MAPPING = {
|
|
ValidationError: "E020",
|
|
InvalidInputDataError: "E020",
|
|
MissingRequiredFieldError: "E021",
|
|
InvalidFieldValueError: "E022",
|
|
DataSizeExceededError: "E023",
|
|
InvalidDataFormatError: "E024",
|
|
DataDuplicationError: "E025",
|
|
UniquenessViolationError: "E026",
|
|
SerializationError: "E027",
|
|
DeserializationError: "E028",
|
|
DatabaseConstraintViolationError: "E029",
|
|
|
|
TransportError: "E040",
|
|
NetworkError: "E040",
|
|
ConnectionTimeoutError: "E041",
|
|
ServiceUnavailableTransportError: "E042",
|
|
DNSResolutionError: "E043",
|
|
SSLCertificateError: "E044",
|
|
RateLimitExceededError: "E045",
|
|
HTTPRequestError: "E046",
|
|
InvalidURLError: "E047",
|
|
ConnectionError: "E048",
|
|
CorruptedResponseError: "E049",
|
|
|
|
IntegrationError: "E060",
|
|
ExternalServiceUnavailableError: "E060",
|
|
ExternalServiceAuthError: "E061",
|
|
InvalidExternalServiceResponseError: "E062",
|
|
DataMappingError: "E063",
|
|
ExternalServiceTimeoutError: "E064",
|
|
ProtocolTranslationError: "E065",
|
|
APIContractViolationError: "E066",
|
|
APIVersioningError: "E067",
|
|
DataMarshallingError: "E068",
|
|
APICompatibilityError: "E069",
|
|
}
|
|
|
|
|
|
class ErrorMapping:
|
|
"""
|
|
Класс для маппинга исключений между 1С и Python системами
|
|
|
|
Обеспечивает:
|
|
- Трансляцию кодов ошибок
|
|
- Сохранение контекста и метаданных
|
|
- Нормализацию сообщений
|
|
- Поддержку correlation_id
|
|
"""
|
|
|
|
def __init__(self, config: Optional[ErrorMappingConfig] = None):
|
|
self.config = config or ErrorMappingConfig()
|
|
|
|
def python_to_1c(self, error: Exception, correlation_id: Optional[str] = None) -> Dict[str, Any]:
|
|
"""
|
|
Транслирует Python исключение в формат 1С
|
|
|
|
Args:
|
|
error: Python исключение
|
|
correlation_id: Идентификатор корреляции
|
|
|
|
Returns:
|
|
Dict[str, Any]: Данные исключения в формате 1С
|
|
"""
|
|
correlation_id = correlation_id or str(uuid.uuid4())
|
|
|
|
# Определяем код ошибки
|
|
error_code = self._get_error_code(error)
|
|
|
|
# Определяем категорию
|
|
category = self._get_category(error)
|
|
|
|
# Формируем базовую структуру
|
|
result = {
|
|
"КодОшибки": error_code,
|
|
"Категория": category.value,
|
|
"Контекст": "",
|
|
"ПользовательскоеСообщение": str(error),
|
|
"ТехническоеСообщение": str(error),
|
|
"Восстановимое": getattr(error, 'recoverable', True),
|
|
"TraceId": correlation_id,
|
|
"ВремяСоздания": getattr(error, 'timestamp', None),
|
|
"Пользователь": "",
|
|
"Соединение": "",
|
|
"ДополнительныеПараметры": {}
|
|
}
|
|
|
|
# Добавляем контекстные данные
|
|
if isinstance(error, McpError):
|
|
result["Контекст"] = error.context
|
|
result["ТехническоеСообщение"] = error.technical_message
|
|
result["ДополнительныеПараметры"] = error.context_data.copy()
|
|
|
|
# Добавляем дополнительную информацию
|
|
if hasattr(error, '__cause__') and error.__cause__:
|
|
result["ДополнительныеПараметры"]["Причина"] = str(error.__cause__)
|
|
|
|
if hasattr(error, '__traceback__') and error.__traceback__:
|
|
result["ДополнительныеПараметры"]["Трассировка"] = self._format_traceback(error.__traceback__)
|
|
|
|
return result
|
|
|
|
def _1c_to_python(self, error_data: Dict[str, Any]) -> McpError:
|
|
"""
|
|
Транслирует данные исключения из 1С в Python исключение
|
|
|
|
Args:
|
|
error_data: Данные исключения из 1С
|
|
|
|
Returns:
|
|
McpError: Соответствующее Python исключение
|
|
"""
|
|
error_code = error_data.get("КодОшибки", "")
|
|
category = error_data.get("Категория", "")
|
|
user_message = error_data.get("ПользовательскоеСообщение", "")
|
|
context = error_data.get("Контекст", "")
|
|
additional_params = error_data.get("ДополнительныеПараметры", {})
|
|
recoverable = error_data.get("Восстановимое", True)
|
|
|
|
# Определяем тип исключения на основе кода ошибки
|
|
error_class = self._get_error_class(error_code, category)
|
|
|
|
# Создаем соответствующее исключение
|
|
if error_class in [ValidationError, InvalidInputDataError, MissingRequiredFieldError]:
|
|
field_name = additional_params.get("field_name", "")
|
|
field_value = additional_params.get("field_value", None)
|
|
return error_class(
|
|
error_code=error_code,
|
|
field_name=field_name,
|
|
field_value=field_value,
|
|
user_message=user_message,
|
|
recoverable=recoverable,
|
|
context_data=additional_params
|
|
)
|
|
|
|
elif error_class in [TransportError, NetworkError, ConnectionTimeoutError]:
|
|
url = additional_params.get("url", "")
|
|
method = additional_params.get("method", "GET")
|
|
return error_class(
|
|
error_code=error_code,
|
|
url=url,
|
|
method=method,
|
|
user_message=user_message,
|
|
recoverable=recoverable,
|
|
context_data=additional_params
|
|
)
|
|
|
|
elif error_class in [IntegrationError, ExternalServiceUnavailableError]:
|
|
service_name = additional_params.get("service_name", "")
|
|
api_version = additional_params.get("api_version", "")
|
|
return error_class(
|
|
error_code=error_code,
|
|
service_name=service_name,
|
|
api_version=api_version,
|
|
user_message=user_message,
|
|
recoverable=recoverable,
|
|
context_data=additional_params
|
|
)
|
|
|
|
else:
|
|
# Базовое исключение
|
|
return McpError(
|
|
error_code=error_code,
|
|
error_type=error_class.__name__,
|
|
user_message=user_message,
|
|
context=context,
|
|
recoverable=recoverable,
|
|
context_data=additional_params
|
|
)
|
|
|
|
def _get_error_code(self, error: Exception) -> str:
|
|
"""Определяет код ошибки для Python исключения"""
|
|
if isinstance(error, McpError):
|
|
return error.error_code
|
|
|
|
# Поиск в маппинге
|
|
for python_class, one_c_code in self.config.PYTHON_TO_1C_MAPPING.items():
|
|
if isinstance(error, python_class):
|
|
return one_c_code
|
|
|
|
# По умолчанию системная ошибка
|
|
return "E001"
|
|
|
|
def _get_category(self, error: Exception) -> ErrorCategory:
|
|
"""Определяет категорию ошибки"""
|
|
if isinstance(error, McpError):
|
|
return error.category
|
|
|
|
# Поиск в маппинге
|
|
for python_class, (error_code, category) in self.config.ERROR_CODE_MAPPING.items():
|
|
if isinstance(error, python_class):
|
|
return category
|
|
|
|
return ErrorCategory.SYSTEM
|
|
|
|
def _get_error_class(self, error_code: str, category: str) -> Type[McpError]:
|
|
"""Определяет класс исключения на основе кода ошибки и категории"""
|
|
|
|
# Точные соответствия
|
|
mapping = {
|
|
# Валидация
|
|
"E020": InvalidInputDataError,
|
|
"E021": MissingRequiredFieldError,
|
|
"E022": InvalidFieldValueError,
|
|
"E023": DataSizeExceededError,
|
|
"E024": InvalidDataFormatError,
|
|
"E025": DataDuplicationError,
|
|
"E026": UniquenessViolationError,
|
|
"E027": SerializationError,
|
|
"E028": DeserializationError,
|
|
"E029": DatabaseConstraintViolationError,
|
|
|
|
# Транспорт
|
|
"E040": NetworkError,
|
|
"E041": ConnectionTimeoutError,
|
|
"E042": ServiceUnavailableTransportError,
|
|
"E043": DNSResolutionError,
|
|
"E044": SSLCertificateError,
|
|
"E045": RateLimitExceededError,
|
|
"E046": HTTPRequestError,
|
|
"E047": InvalidURLError,
|
|
"E048": ConnectionError,
|
|
"E049": CorruptedResponseError,
|
|
|
|
# Интеграция
|
|
"E060": ExternalServiceUnavailableError,
|
|
"E061": ExternalServiceAuthError,
|
|
"E062": InvalidExternalServiceResponseError,
|
|
"E063": DataMappingError,
|
|
"E064": ExternalServiceTimeoutError,
|
|
"E065": ProtocolTranslationError,
|
|
"E066": APIContractViolationError,
|
|
"E067": APIVersioningError,
|
|
"E068": DataMarshallingError,
|
|
"E069": APICompatibilityError,
|
|
}
|
|
|
|
return mapping.get(error_code, McpError)
|
|
|
|
def _format_traceback(self, tb) -> str:
|
|
"""Форматирует traceback для передачи между системами"""
|
|
import traceback
|
|
return ''.join(traceback.format_tb(tb))
|
|
|
|
def normalize_error_message(self, error: Exception, source_system: str) -> str:
|
|
"""
|
|
Нормализует сообщение об ошибке для跨системного использования
|
|
|
|
Args:
|
|
error: Исключение
|
|
source_system: Источник системы ("1c" или "python")
|
|
|
|
Returns:
|
|
str: Нормализованное сообщение
|
|
"""
|
|
if source_system == "1c":
|
|
# Нормализация сообщения из 1С
|
|
message = str(error)
|
|
# Удаляем служебную информацию 1С
|
|
message = message.replace("Ошибка:", "").strip()
|
|
return message
|
|
|
|
elif source_system == "python":
|
|
# Нормализация сообщения Python
|
|
if isinstance(error, McpError):
|
|
return error.user_message
|
|
else:
|
|
return str(error)
|
|
|
|
return str(error)
|
|
|
|
def enrich_error_context(self, error: Exception, additional_context: Dict[str, Any]) -> McpError:
|
|
"""
|
|
Обогащает контекст ошибки дополнительными данными
|
|
|
|
Args:
|
|
error: Исходное исключение
|
|
additional_context: Дополнительный контекст
|
|
|
|
Returns:
|
|
McpError: Обогащенное исключение
|
|
"""
|
|
if isinstance(error, McpError):
|
|
# Обновляем контекстные данные
|
|
error.context_data.update(additional_context)
|
|
return error
|
|
else:
|
|
# Оборачиваем в McpError
|
|
return McpError(
|
|
error_code=self._get_error_code(error),
|
|
error_type=type(error).__name__,
|
|
user_message=str(error),
|
|
context_data=additional_context.copy()
|
|
)
|
|
|
|
|
|
class CrossSystemErrorHandler:
|
|
"""
|
|
Обработчик ошибок для межсистемного взаимодействия
|
|
|
|
Обеспечивает:
|
|
- Логирование ошибок с корреляцией
|
|
- Трансляцию между системами
|
|
- Сохранение контекста операций
|
|
"""
|
|
|
|
def __init__(self, mapping: Optional[ErrorMapping] = None):
|
|
self.mapping = mapping or ErrorMapping()
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
def handle_error(
|
|
self,
|
|
error: Exception,
|
|
operation: str,
|
|
system_boundary: str = "api",
|
|
correlation_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Обрабатывает ошибку для межсистемного взаимодействия
|
|
|
|
Args:
|
|
error: Исключение
|
|
operation: Выполняемая операция
|
|
system_boundary: Граница системы ("api", "database", "external")
|
|
correlation_id: Идентификатор корреляции
|
|
|
|
Returns:
|
|
Dict[str, Any]: Нормализованные данные ошибки
|
|
"""
|
|
correlation_id = correlation_id or str(uuid.uuid4())
|
|
|
|
# Добавляем контекст операции
|
|
additional_context = {
|
|
"operation": operation,
|
|
"system_boundary": system_boundary,
|
|
"handled_at": str(uuid.uuid4()) # Для отслеживания обработки
|
|
}
|
|
|
|
# Обогащаем исключение контекстом
|
|
enriched_error = self.mapping.enrich_error_context(error, additional_context)
|
|
|
|
# Логируем ошибку
|
|
if isinstance(enriched_error, McpError):
|
|
enriched_error.with_correlation_id(correlation_id).log(self.logger)
|
|
else:
|
|
self.logger.error(
|
|
f"Error in {operation} [{correlation_id}]: {enriched_error}",
|
|
extra={
|
|
"correlation_id": correlation_id,
|
|
"operation": operation,
|
|
"system_boundary": system_boundary
|
|
}
|
|
)
|
|
|
|
# Транслируем в формат 1С для хранения/логирования
|
|
error_data = self.mapping.python_to_1c(enriched_error, correlation_id)
|
|
|
|
return error_data
|
|
|
|
def translate_for_api_response(
|
|
self,
|
|
error: Exception,
|
|
correlation_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Подготавливает ошибку для ответа API
|
|
|
|
Args:
|
|
error: Исключение
|
|
correlation_id: Идентификатор корреляции
|
|
|
|
Returns:
|
|
Dict[str, Any]: Ответ в формате RFC 7807
|
|
"""
|
|
correlation_id = correlation_id or str(uuid.uuid4())
|
|
|
|
# Транслируем исключение
|
|
if isinstance(error, McpError):
|
|
# Уже подготовлено для MCP
|
|
response_data = error.to_mcp_response()
|
|
else:
|
|
# Создаем базовое исключение
|
|
base_error = McpError(
|
|
error_code="E001",
|
|
error_type=type(error).__name__,
|
|
user_message=str(error),
|
|
correlation_id=correlation_id
|
|
)
|
|
response_data = base_error.to_mcp_response()
|
|
|
|
# Добавляем метаданные для отладки
|
|
if hasattr(error, '__traceback__'):
|
|
response_data["error"]["data"]["debug_info"] = {
|
|
"traceback_available": True,
|
|
"requires_debug_mode": True
|
|
}
|
|
|
|
return response_data
|
|
|
|
|
|
# Экспортируемые экземпляры
|
|
default_error_mapping = ErrorMapping()
|
|
default_error_handler = CrossSystemErrorHandler(default_error_mapping)
|
|
|
|
|
|
# Утилитарные функции
|
|
def translate_1c_error_to_python(error_data: Dict[str, Any]) -> McpError:
|
|
"""Утилита для быстрой трансляции ошибки 1С в Python"""
|
|
return default_error_mapping._1c_to_python(error_data)
|
|
|
|
|
|
def translate_python_error_to_1c(error: Exception, correlation_id: Optional[str] = None) -> Dict[str, Any]:
|
|
"""Утилита для быстрой трансляции ошибки Python в 1С"""
|
|
return default_error_mapping.python_to_1c(error, correlation_id)
|
|
|
|
|
|
def handle_api_error(
|
|
error: Exception,
|
|
operation: str,
|
|
correlation_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Утилита для обработки ошибок API"""
|
|
return default_error_handler.handle_error(error, operation, "api", correlation_id)
|
|
|
|
|
|
def prepare_api_error_response(
|
|
error: Exception,
|
|
correlation_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Утилита для подготовки ответа API с ошибкой"""
|
|
return default_error_handler.translate_for_api_response(error, correlation_id) |