| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | import re | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 12:34:01 +08:00
										 |  |  | from flask import current_app, got_request_exception | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | from flask_restful import Api, http_status_message | 
					
						
							|  |  |  | from werkzeug.datastructures import Headers | 
					
						
							|  |  |  | from werkzeug.exceptions import HTTPException | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-10 13:31:35 +00:00
										 |  |  | from core.errors.error import AppInvokeQuotaExceededError | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class ExternalApi(Api): | 
					
						
							|  |  |  |     def handle_error(self, e): | 
					
						
							|  |  |  |         """Error handler for the API transforms a raised exception into a Flask
 | 
					
						
							|  |  |  |         response, with the appropriate HTTP status code and body. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         :param e: the raised Exception object | 
					
						
							|  |  |  |         :type e: Exception | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         got_request_exception.send(current_app, exception=e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         headers = Headers() | 
					
						
							|  |  |  |         if isinstance(e, HTTPException): | 
					
						
							|  |  |  |             if e.response is not None: | 
					
						
							|  |  |  |                 resp = e.get_response() | 
					
						
							|  |  |  |                 return resp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             status_code = e.code | 
					
						
							|  |  |  |             default_data = { | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |                 "code": re.sub(r"(?<!^)(?=[A-Z])", "_", type(e).__name__).lower(), | 
					
						
							|  |  |  |                 "message": getattr(e, "description", http_status_message(status_code)), | 
					
						
							|  |  |  |                 "status": status_code, | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-01-17 22:39:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |             if ( | 
					
						
							|  |  |  |                 default_data["message"] | 
					
						
							|  |  |  |                 and default_data["message"] == "Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)" | 
					
						
							|  |  |  |             ): | 
					
						
							|  |  |  |                 default_data["message"] = "Invalid JSON payload received or JSON payload is empty." | 
					
						
							| 
									
										
										
										
											2024-01-17 22:39:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |             headers = e.get_response().headers | 
					
						
							|  |  |  |         elif isinstance(e, ValueError): | 
					
						
							|  |  |  |             status_code = 400 | 
					
						
							|  |  |  |             default_data = { | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |                 "code": "invalid_param", | 
					
						
							|  |  |  |                 "message": str(e), | 
					
						
							|  |  |  |                 "status": status_code, | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-07-10 13:31:35 +00:00
										 |  |  |         elif isinstance(e, AppInvokeQuotaExceededError): | 
					
						
							|  |  |  |             status_code = 429 | 
					
						
							|  |  |  |             default_data = { | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |                 "code": "too_many_requests", | 
					
						
							|  |  |  |                 "message": str(e), | 
					
						
							|  |  |  |                 "status": status_code, | 
					
						
							| 
									
										
										
										
											2024-07-10 13:31:35 +00:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |         else: | 
					
						
							|  |  |  |             status_code = 500 | 
					
						
							|  |  |  |             default_data = { | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |                 "message": http_status_message(status_code), | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Werkzeug exceptions generate a content-length header which is added | 
					
						
							|  |  |  |         # to the response in addition to the actual content-length header | 
					
						
							|  |  |  |         # https://github.com/flask-restful/flask-restful/issues/534 | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |         remove_headers = ("Content-Length",) | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for header in remove_headers: | 
					
						
							|  |  |  |             headers.pop(header, None) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |         data = getattr(e, "data", default_data) | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         error_cls_name = type(e).__name__ | 
					
						
							|  |  |  |         if error_cls_name in self.errors: | 
					
						
							|  |  |  |             custom_data = self.errors.get(error_cls_name, {}) | 
					
						
							|  |  |  |             custom_data = custom_data.copy() | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |             status_code = custom_data.get("status", 500) | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |             if "message" in custom_data: | 
					
						
							|  |  |  |                 custom_data["message"] = custom_data["message"].format( | 
					
						
							|  |  |  |                     message=str(e.description if hasattr(e, "description") else e) | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |                 ) | 
					
						
							|  |  |  |             data.update(custom_data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # record the exception in the logs when we have a server error of status code: 500 | 
					
						
							|  |  |  |         if status_code and status_code >= 500: | 
					
						
							|  |  |  |             exc_info = sys.exc_info() | 
					
						
							|  |  |  |             if exc_info[1] is None: | 
					
						
							|  |  |  |                 exc_info = None | 
					
						
							|  |  |  |             current_app.log_exception(exc_info) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if status_code == 406 and self.default_mediatype is None: | 
					
						
							|  |  |  |             # if we are handling NotAcceptable (406), make sure that | 
					
						
							|  |  |  |             # make_response uses a representation we support as the | 
					
						
							|  |  |  |             # default mediatype (so that make_response doesn't throw | 
					
						
							|  |  |  |             # another NotAcceptable error). | 
					
						
							|  |  |  |             supported_mediatypes = list(self.representations.keys())  # only supported application/json | 
					
						
							|  |  |  |             fallback_mediatype = supported_mediatypes[0] if supported_mediatypes else "text/plain" | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |             data = {"code": "not_acceptable", "message": data.get("message")} | 
					
						
							|  |  |  |             resp = self.make_response(data, status_code, headers, fallback_mediatype=fallback_mediatype) | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |         elif status_code == 400: | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |             if isinstance(data.get("message"), dict): | 
					
						
							|  |  |  |                 param_key, param_value = list(data.get("message").items())[0] | 
					
						
							|  |  |  |                 data = {"code": "invalid_param", "message": param_value, "params": param_key} | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |                 if "code" not in data: | 
					
						
							|  |  |  |                     data["code"] = "unknown" | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             resp = self.make_response(data, status_code, headers) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-08-15 17:53:12 +08:00
										 |  |  |             if "code" not in data: | 
					
						
							|  |  |  |                 data["code"] = "unknown" | 
					
						
							| 
									
										
										
										
											2023-05-15 08:51:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             resp = self.make_response(data, status_code, headers) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if status_code == 401: | 
					
						
							|  |  |  |             resp = self.unauthorized(resp) | 
					
						
							|  |  |  |         return resp |