adapting exception handling and error responses for 400 responses. Using a simplified format, which only uses the keys 'error_field' and 'error_description'
This commit is contained in:
parent
590df30fef
commit
5b68ef95cb
@ -10,6 +10,7 @@ from ..services.auth_guard import check_jwt
|
||||
from BreCal.database.update_database import evaluate_shipcall_state
|
||||
from BreCal.database.sql_queries import create_sql_query_shipcall_get, create_sql_query_shipcall_post, create_sql_query_shipcall_put, create_sql_query_history_post, create_sql_query_history_put, SQLQuery
|
||||
from marshmallow import Schema, fields, ValidationError
|
||||
from BreCal.validators.validation_error import create_validation_error_response
|
||||
|
||||
def GetShipcalls(options):
|
||||
"""
|
||||
@ -152,9 +153,7 @@ def PostShipcalls(schemaModel):
|
||||
return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'}
|
||||
|
||||
except ValidationError as ex:
|
||||
logging.error(ex)
|
||||
print(ex)
|
||||
return json.dumps({"message":f"bad format. \nError Messages: {ex.messages}. \nValid Data: {ex.valid_data}"}), 400
|
||||
return create_validation_error_response(ex, status_code=400, create_log=True)
|
||||
|
||||
except Exception as ex:
|
||||
logging.error(traceback.format_exc())
|
||||
@ -265,9 +264,7 @@ def PutShipcalls(schemaModel):
|
||||
return json.dumps({"id" : schemaModel["id"]}), 200
|
||||
|
||||
except ValidationError as ex:
|
||||
logging.error(ex)
|
||||
print(ex)
|
||||
return json.dumps({"message":f"bad format. \nError Messages: {ex.messages}. \nValid Data: {ex.valid_data}"}), 400
|
||||
return create_validation_error_response(ex, status_code=400, create_log=True)
|
||||
|
||||
except Exception as ex:
|
||||
logging.error(traceback.format_exc())
|
||||
|
||||
@ -1,58 +1,59 @@
|
||||
import logging
|
||||
import typing
|
||||
import json
|
||||
import sys
|
||||
from marshmallow import ValidationError
|
||||
import werkzeug
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
|
||||
def create_default_error_format(error_description):
|
||||
def create_default_json_response_format(error_field:str, error_description:str):
|
||||
"""
|
||||
The default format of a JSON response for exceptions is:
|
||||
{
|
||||
"message":str,
|
||||
"errors":typing.Optional[
|
||||
list[dict[str,list[str]]]
|
||||
]
|
||||
}
|
||||
"""
|
||||
json_response = {
|
||||
"message":f"{error_description}",
|
||||
"errors":[],
|
||||
"valid_data":{}
|
||||
"error_field":error_field,
|
||||
"error_description":error_description
|
||||
}
|
||||
return json_response
|
||||
|
||||
def unbundle_(errors, unbundled=[]):
|
||||
"""
|
||||
unbundles a dictionary entry into separate items and appends them to the list {unbundled}.
|
||||
Example:
|
||||
errors = {"key1":{"keya":"keyb","keyc":{"keyc12":12}}}
|
||||
Returns:
|
||||
[{'error_field':'keya', 'error_description':['keyb']}, {'error_field':'keyc12', 'error_description':[12]}]
|
||||
As can be seen, only the subkeys and their respective value are received. Each value is *always* a list.
|
||||
"""
|
||||
{k:unbundle_(v,unbundled=unbundled) if isinstance(v,dict) else unbundled.append({"error_field":k, "error_description":v[0] if isinstance(v,list) else str(v)}) for k,v in errors.items()}
|
||||
return
|
||||
|
||||
def unbundle_validation_error_message(message):
|
||||
"""
|
||||
inputs:
|
||||
message: ValidationError.messages object. A str, list or dictionary
|
||||
"""
|
||||
unbundled = []
|
||||
unbundle_(message, unbundled=unbundled)
|
||||
if len(unbundled)>0:
|
||||
error_field = "ValidationError in the following field(s): " + " & ".join([unb["error_field"] for unb in unbundled])
|
||||
error_description = "Error Description(s): " + " & ".join([unb["error_description"] for unb in unbundled])
|
||||
else:
|
||||
error_field = "ValidationError"
|
||||
error_description = "unknown validation error"
|
||||
return (error_field, error_description)
|
||||
|
||||
def create_validation_error_response(ex:ValidationError, status_code:int=400, create_log:bool=True)->typing.Tuple[str,int]:
|
||||
# generate an overview the errors
|
||||
#example:
|
||||
# {'lock_time': ['The provided value must be in the future. Current Time: 2024-09-02 08:23:32.600791, Value: 2024-09-01 08:20:41.853000']}
|
||||
# when the model schema returns an error, 'messages' is by default a dictionary.
|
||||
# e.g., loadedModel = model.TimesSchema().load(data=content, many=False, partial=True)
|
||||
# returns: {'eta_berth': ['The provided value must be in the future}
|
||||
|
||||
# when raising a custom ValidationError, it can return a string, list or dict.
|
||||
# we would like to ensure, that the content of the .messages is a dictionary. This can be accomplished by calling
|
||||
# raise ValidationError({"example_key_which_fails":"the respective error message"})
|
||||
errors = ex.messages
|
||||
|
||||
# raise ValidationError("example error")
|
||||
# creates a .messages object, which is an array. e.g., ex.messages = ["example error"]
|
||||
# the following conversion snipped ensures a dictionary output
|
||||
if isinstance(errors, (str,list)):
|
||||
errors = {"undefined_schema":errors}
|
||||
errors = {k:v if isinstance(v,list) else [v] for k,v in errors.items()}
|
||||
|
||||
# hence, errors always has the following type: list[dict[str, list[str]]]
|
||||
errors = [errors]
|
||||
|
||||
|
||||
# example:
|
||||
# "Valid Data": {
|
||||
# "id": 2894,
|
||||
# "eta_berth": "datetime.datetime(2024, 9, 2, 11, 11, 43)",
|
||||
# "eta_berth_fixed": false
|
||||
# }
|
||||
valid_data = ex.valid_data
|
||||
|
||||
message = "ValidationError"
|
||||
json_response = create_default_error_format(error_description=message)
|
||||
json_response.update({
|
||||
"errors":errors,
|
||||
"valid_data":valid_data
|
||||
})
|
||||
# unbundles ValidationError into a dictionary of {'error_field':str, 'error_description':str}-format
|
||||
message = ex.messages
|
||||
(error_field, error_description) = unbundle_validation_error_message(message)
|
||||
json_response = create_default_json_response_format(error_field=error_field, error_description=error_description)
|
||||
|
||||
# json.dumps with default=str automatically converts non-serializable values to strings. Hence, datetime objects (which are not)
|
||||
# natively serializable are properly serialized.
|
||||
@ -67,7 +68,7 @@ def create_werkzeug_error_response(ex:Forbidden, status_code:int=403, create_log
|
||||
# json.dumps with default=str automatically converts non-serializable values to strings. Hence, datetime objects (which are not)
|
||||
# natively serializable are properly serialized.
|
||||
message = ex.description
|
||||
json_response = create_default_error_format(error_description=message)
|
||||
json_response = create_default_json_response_format(error_field=str(repr(ex)), error_description=message)
|
||||
serialized_response = json.dumps(json_response, default=str)
|
||||
|
||||
if create_log:
|
||||
@ -77,7 +78,8 @@ def create_werkzeug_error_response(ex:Forbidden, status_code:int=403, create_log
|
||||
|
||||
def create_dynamic_exception_response(ex, status_code:int=400, message:typing.Optional[str]=None, create_log:bool=True):
|
||||
message = repr(ex) if message is None else message
|
||||
json_response = create_default_error_format(error_description=message)
|
||||
json_response = create_default_json_response_format(error_field="Exception", error_description=message)
|
||||
json_response["message"] = "call failed"
|
||||
|
||||
serialized_response = json.dumps(json_response, default=str)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user