"""Sanitized application errors and HTTP/protocol mappings.""" from __future__ import annotations import re _BEARER_PATTERN = re.compile(r"Bearer\s+[A-Za-z0-9_\-\.]+", re.IGNORECASE) _TOKEN_PATTERN = re.compile(r"\b[A-Za-z0-9_\-]{32,}\b") def sanitize_message(msg: str, max_bytes: int = 1024) -> str: """Sanitize and bound an error message to max_bytes UTF-8, stripping secret lookalikes.""" if not msg: return "" # Strip potential token/secret lookalikes cleaned = _BEARER_PATTERN.sub("Bearer [REDACTED]", msg) cleaned = _TOKEN_PATTERN.sub("[REDACTED]", cleaned) # Strip newlines and carriage returns cleaned = cleaned.replace("\r", " ").replace("\n", " ") encoded = cleaned.encode("utf-8") if len(encoded) <= max_bytes: return cleaned truncated = encoded[:max_bytes] # Ensure valid UTF-8 boundary return truncated.decode("utf-8", errors="ignore") class AppError(Exception): """Base application error with sanitized error code and message.""" def __init__(self, code: str, message: str, status_code: int = 500): super().__init__(message) self.code = code self.message = sanitize_message(message) self.status_code = status_code def to_error_dict(self) -> dict: return { "code": self.code, "message": self.message, } def to_envelope(self) -> dict: return { "error": self.to_error_dict(), } class InvalidInputError(AppError): def __init__(self, message: str = "Invalid input"): super().__init__("invalid_input", message, status_code=400) class ModelContextExceededError(AppError): def __init__(self, message: str = "Model context window exceeded"): super().__init__("model_context_exceeded", message, status_code=400) class OriginDeniedError(AppError): def __init__(self, message: str = "Origin denied by policy"): super().__init__("origin_denied", message, status_code=403) class DestinationDeniedError(AppError): def __init__(self, message: str = "Destination denied by policy"): super().__init__("destination_denied", message, status_code=403) class ConfluenceAuthFailedError(AppError): def __init__(self, message: str = "Confluence authentication failed"): super().__init__("confluence_auth_failed", message, status_code=403) class ArtifactNotFoundError(AppError): def __init__(self, message: str = "Artifact not found"): super().__init__("artifact_not_found", message, status_code=404) class BusyError(AppError): def __init__(self, message: str = "Active query already in progress"): super().__init__("busy", message, status_code=409) class RequestTooLargeError(AppError): def __init__(self, message: str = "Request body exceeds maximum allowed size"): super().__init__("request_too_large", message, status_code=413) class UpstreamFailedError(AppError): def __init__(self, message: str = "Upstream request failed"): super().__init__("upstream_failed", message, status_code=502) class UpstreamResponseTooLargeError(AppError): def __init__(self, message: str = "Upstream response exceeds maximum allowed size"): super().__init__("upstream_response_too_large", message, status_code=502) class TlsFailedError(AppError): def __init__(self, message: str = "TLS certificate verification failed"): super().__init__("tls_failed", message, status_code=502) class ConnectivityFailedError(AppError): def __init__(self, message: str = "Failed to connect to upstream server"): super().__init__("connectivity_failed", message, status_code=502) class ModelOutputLimitError(AppError): def __init__(self, message: str = "Model output exceeded limit"): super().__init__("model_output_limit", message, status_code=502) class QueryTimeoutError(AppError): def __init__(self, message: str = "Query execution timed out"): super().__init__("query_timeout", message, status_code=504) class ExecutionFailedError(AppError): def __init__(self, message: str = "Query execution failed"): super().__init__("execution_failed", message, status_code=500) class CleanupFailedError(AppError): def __init__(self, message: str = "Container cleanup could not be confirmed"): super().__init__("cleanup_failed", message, status_code=500)