Underground-Digital/Workflow-Engine
0
1import re2import sys3 4from flask import current_app, got_request_exception5from flask_restful import Api, http_status_message6from werkzeug.datastructures import Headers7from werkzeug.exceptions import HTTPException8 9from core.errors.error import AppInvokeQuotaExceededError10 11 12class ExternalApi(Api):13 def handle_error(self, e):14 """Error handler for the API transforms a raised exception into a Flask15 response, with the appropriate HTTP status code and body.16 17 :param e: the raised Exception object18 :type e: Exception19 20 """21 got_request_exception.send(current_app, exception=e)22 23 headers = Headers()24 if isinstance(e, HTTPException):25 if e.response is not None:26 resp = e.get_response()27 return resp28 29 status_code = e.code30 default_data = {31 "code": re.sub(r"(?<!^)(?=[A-Z])", "_", type(e).__name__).lower(),32 "message": getattr(e, "description", http_status_message(status_code)),33 "status": status_code,34 }35 36 if (37 default_data["message"]38 and default_data["message"] == "Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)"39 ):40 default_data["message"] = "Invalid JSON payload received or JSON payload is empty."41 42 headers = e.get_response().headers43 elif isinstance(e, ValueError):44 status_code = 40045 default_data = {46 "code": "invalid_param",47 "message": str(e),48 "status": status_code,49 }50 elif isinstance(e, AppInvokeQuotaExceededError):51 status_code = 42952 default_data = {53 "code": "too_many_requests",54 "message": str(e),55 "status": status_code,56 }57 else:58 status_code = 50059 default_data = {60 "message": http_status_message(status_code),61 }62 63 # Werkzeug exceptions generate a content-length header which is added64 # to the response in addition to the actual content-length header65 # https://github.com/flask-restful/flask-restful/issues/53466 remove_headers = ("Content-Length",)67 68 for header in remove_headers:69 headers.pop(header, None)70 71 data = getattr(e, "data", default_data)72 73 error_cls_name = type(e).__name__74 if error_cls_name in self.errors:75 custom_data = self.errors.get(error_cls_name, {})76 custom_data = custom_data.copy()77 status_code = custom_data.get("status", 500)78 79 if "message" in custom_data:80 custom_data["message"] = custom_data["message"].format(81 message=str(e.description if hasattr(e, "description") else e)82 )83 data.update(custom_data)84 85 # record the exception in the logs when we have a server error of status code: 50086 if status_code and status_code >= 500:87 exc_info = sys.exc_info()88 if exc_info[1] is None:89 exc_info = None90 current_app.log_exception(exc_info)91 92 if status_code == 406 and self.default_mediatype is None:93 # if we are handling NotAcceptable (406), make sure that94 # make_response uses a representation we support as the95 # default mediatype (so that make_response doesn't throw96 # another NotAcceptable error).97 supported_mediatypes = list(self.representations.keys()) # only supported application/json98 fallback_mediatype = supported_mediatypes[0] if supported_mediatypes else "text/plain"99 data = {"code": "not_acceptable", "message": data.get("message")}100 resp = self.make_response(data, status_code, headers, fallback_mediatype=fallback_mediatype)101 elif status_code == 400:102 if isinstance(data.get("message"), dict):103 param_key, param_value = list(data.get("message").items())[0]104 data = {"code": "invalid_param", "message": param_value, "params": param_key}105 else:106 if "code" not in data:107 data["code"] = "unknown"108 109 resp = self.make_response(data, status_code, headers)110 else:111 if "code" not in data:112 data["code"] = "unknown"113 114 resp = self.make_response(data, status_code, headers)115 116 if status_code == 401:117 resp = self.unauthorized(resp)118 return resp119 