Skip to content

chore(#82): Consistent Error Presentation #586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/api/routes/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
def unauthorized_response():
message = "The email or password you submitted is incorrect " \
"or your account is not allowed api access"
payload = {'errors': {"unauthorized": {"message": message}}}
payload = {'errors': [{"unauthorized": {"message": message}}]}
return utils.standardize_response(payload=payload, status_code=401)


Expand Down
2 changes: 1 addition & 1 deletion app/api/routes/resource_retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def get_resources():
except Exception as e:
logger.exception(e)
message = 'The value for "updated_after" is invalid'
res = {"errors": {"unprocessable-entity": {"message": message}}}
res = {"errors": [{"unprocessable-entity": {"message": message}}]}
return utils.standardize_response(payload=res, status_code=422)

q = q.filter(
Expand Down
39 changes: 19 additions & 20 deletions app/api/validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,19 @@

def requires_body(func):
def wrapper(*args, **kwargs):
try:
# JSON body is {} or []
if not request.get_json():
return missing_json_error()
except Exception as e:
# JSON body is completely missing
if "Expecting value: line 1 column 1" in str(e):
return missing_json_error()

# JSON body is {} or []
# This will throw a 400 if the JSON is malformed
# and drop into handlers.bad_request()
if not request.get_json():
return missing_json_error()
Comment on lines +13 to +17
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaron-junot Is there a way to check if this has had the desired effect on the cognitive complexity in Code Climate whilst I am on a fork?

return func(*args, **kwargs)
return wrapper


def missing_json_error():
message = "You must provide a valid JSON body to use this endpoint"
error = {'errors': [{MISSING_BODY: {"message": message}}]}
return standardize_response(error, status_code=422)
return standardize_response(payload=error, status_code=422)


def validate_resource_list(method, rlist):
Expand All @@ -38,18 +34,19 @@ def validate_resource_list(method, rlist):
return {"errors": [{"too-long": {"message": msg}}]}

for i, r in enumerate(rlist):
validation = validate_resource(method, r)
if validation:
validation['index'] = i
errors['errors'].append(validation)
validation_errors = validate_resource(method, r)
if validation_errors:
for err in validation_errors:
err['index'] = i
errors['errors'].append(err)

if bool(errors['errors']):
return errors


def validate_resource(method, json, id=-1):
errors = None
validation_errors = {}
validation_errors = []
missing_params = {"params": []}
invalid_params = {"params": []}
required = []
Expand Down Expand Up @@ -115,18 +112,20 @@ def validate_resource(method, json, id=-1):
f"https://resources.operationcode.org/api/v1/{resource.id}"

if missing_params["params"]:
validation_errors[MISSING_PARAMS] = missing_params
missing_params_error = {MISSING_PARAMS: missing_params}
msg = " The following params were missing: "
msg += ", ".join(missing_params.get("params")) + "."
validation_errors[MISSING_PARAMS]["message"] = msg
missing_params_error[MISSING_PARAMS]["message"] = msg
validation_errors.append(missing_params_error)
errors = True

if invalid_params["params"]:
validation_errors[INVALID_PARAMS] = invalid_params
invalid_params_error = {INVALID_PARAMS: invalid_params}
msg = " The following params were invalid: "
msg += ", ".join(invalid_params.get("params")) + ". "
msg += invalid_params.get("message", "")
validation_errors[INVALID_PARAMS]["message"] = msg.strip()
invalid_params_error[INVALID_PARAMS]["message"] = msg.strip()
validation_errors.append(invalid_params_error)
errors = True

if errors:
Expand All @@ -144,6 +143,6 @@ def wrong_type(type_accepted, type_provided):
}
json_type = types[type_provided]
msg = f"Expected {type_accepted}, but found {json_type}"
validation_errors = {"errors": {INVALID_TYPE: {"message": msg}}}
validation_errors = {"errors": [{INVALID_TYPE: {"message": msg}}]}

return standardize_response(payload=validation_errors, status_code=422)
2 changes: 1 addition & 1 deletion app/errors/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Error Handlers
@bp.app_errorhandler(400)
def bad_request(e):
errors = {"errors": {"bad-request": {"message": str(e)}}}
errors = {"errors": [{"bad-request": {"message": str(e)}}]}
return standardize_response(payload=errors, status_code=400)


Expand Down
6 changes: 3 additions & 3 deletions app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,13 @@ def standardize_response(
else:
code = get_error_code_from_status(status_code)
message = msg_map.get(status_code)
resp["errors"] = {}
resp["errors"][code] = {"message": message}
resp["errors"] = []
resp["errors"].append({code: {"message": message}})

elif not data:
# 500 Error case -- Something went wrong.
message = msg_map.get(500)
resp["errors"] = {"server-error": {"message": message}}
resp["errors"] = [{"server-error": {"message": message}}]
resp["status_code"] = 500
resp["status"] = err_map.get(500)
else:
Expand Down
32 changes: 23 additions & 9 deletions tests/unit/test_routes/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,29 @@ def get_api_key(client):

def assert_correct_response(response, code):
assert (response.status_code == code)
assert (isinstance(response.json.get('errors').get(
get_error_code_from_status(response.status_code)), dict))
assert (isinstance(response.json.get('errors').get(
get_error_code_from_status(response.status_code)).get('message'), str))
assert (isinstance(
response.json.get('errors')[0].get(
get_error_code_from_status(response.status_code)
),
dict))
assert (isinstance(
response.json.get('errors')[0].get(
get_error_code_from_status(response.status_code)
).get('message'),
str))


def assert_correct_validation_error(response, params):
assert (response.status_code == 422)
assert (isinstance(response.json.get('errors')
assert (isinstance(response.json.get('errors')[0]
.get(INVALID_PARAMS), dict))
assert (isinstance(response.json.get('errors')
assert (isinstance(response.json.get('errors')[0]
.get(INVALID_PARAMS).get('message'), str))

for param in params:
assert (param in response.json.get('errors')
assert (param in response.json.get('errors')[0]
.get(INVALID_PARAMS).get("params"))
assert (param in response.json.get('errors')
assert (param in response.json.get('errors')[0]
.get(INVALID_PARAMS).get("message"))


Expand Down Expand Up @@ -146,4 +152,12 @@ def assert_missing_params_create(response, params, index):
def assert_wrong_type(response, expected_type):
assert (response.status_code == 422)
assert (expected_type in response.get_json()
.get("errors").get(INVALID_TYPE).get("message"))
.get("errors")[0].get(INVALID_TYPE).get("message"))


def assert_bad_request(response):
assert (response.status_code == 400)
assert (isinstance(response.json.get('errors')[0]
.get('bad-request'), dict))
assert (isinstance(response.json.get('errors')[0]
.get('bad-request').get('message'), str))
8 changes: 4 additions & 4 deletions tests/unit/test_routes/test_invalid_put_post.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .helpers import (
create_resource, get_api_key, assert_correct_response,
assert_correct_validation_error, assert_missing_body
assert_correct_validation_error, assert_missing_body, assert_bad_request
)


Expand Down Expand Up @@ -97,15 +97,15 @@ def test_validate_resource(module_client, module_db, fake_auth_from_oc):
)
assert_missing_body(response)

# Data cannot be empty
# Data must be valid json
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test was not quite working as intended previously. Passing in data='' is not the same as not passing in data at all. This ultimately gets treated as a 400 bad request as there is a payload, but it's not valid json.

response = client.put(
"api/v1/resources/1",
data='',
data='this is not json',
content_type='application/json',
headers={'x-apikey': apikey},
follow_redirects=True
)
assert_missing_body(response)
assert_bad_request(response)


def test_create_with_invalid_apikey(module_client, module_db):
Expand Down