import re from collections.abc import Mapping from typing import Any, Protocol, override from flask import Blueprint, Flask, current_app, got_request_exception, request from flask_restx import Api from werkzeug.exceptions import HTTPException from werkzeug.http import HTTP_STATUS_CODES from configs import dify_config from core.errors.error import AppInvokeQuotaExceededError from libs.flask_restx_compat import patch_swagger_for_inline_nested_dicts from libs.token import build_force_logout_cookie_headers def http_status_message(code): return HTTP_STATUS_CODES.get(code, "") class ErrorBodyFormatter(Protocol): """Last-touch hook over an error body before it goes on the wire.""" def finalize(self, e: Exception, data: dict[str, Any], status_code: int) -> dict[str, Any]: ... def register_external_error_handlers(api: Api, body_formatter: ErrorBodyFormatter | None = None): def _finalize(e: Exception, data: dict[str, Any], status_code: int) -> dict[str, Any]: if body_formatter is None: return data return body_formatter.finalize(e, data, status_code) def handle_http_exception(e: HTTPException): got_request_exception.send(current_app, exception=e) # If Werkzeug already prepared a Response, just use it. This bypasses # body_formatter entirely — surfaces with a formatter must not raise # exceptions carrying a pre-built response. if e.response is not None: return e.response status_code = getattr(e, "code", 500) or 500 # Build a safe, dict-like payload default_data = { "code": re.sub(r"(? bool: prefix = self.blueprint.url_prefix if self.blueprint is not None else None if not prefix: return True return request.path == prefix or request.path.startswith(prefix.rstrip("/") + "/") @override def _help_on_404(self, message: str | None = None) -> str | None: # flask-restx appends route suggestions post-handler; with a canonical # formatter installed, that would corrupt the contract and enumerate # routes to unauthenticated callers. if self._error_body_formatter is not None: return message return super()._help_on_404(message)