Skip to content

Commit

Permalink
Handle case when 422 error does not have a 'field' value (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmhossler authored and brettcannon committed Jan 21, 2020
1 parent 0f0af73 commit ddc715a
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 5 deletions.
14 changes: 14 additions & 0 deletions gidgethub/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ def __init__(self, errors: Any, *args: Any) -> None:
super().__init__(http.HTTPStatus.UNPROCESSABLE_ENTITY, *args)


class ValidationError(BadRequest):

"""A request was unable to be completed.
Represented by a 422 HTTP Response. Details of what went wrong
are stored in the errors attribute.
"""

def __init__(self, errors: Any, *args: Any) -> None:
"""Store the error details."""
self.errors = errors
super().__init__(http.HTTPStatus.UNPROCESSABLE_ENTITY, *args)


class GitHubBroken(HTTPException):

"""Exception for 5XX HTTP responses."""
16 changes: 12 additions & 4 deletions gidgethub/sansio.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
import uritemplate

from . import (BadRequest, GitHubBroken, HTTPException, InvalidField,
RateLimitExceeded, RedirectionException, ValidationFailure)
RateLimitExceeded, RedirectionException, ValidationError,
ValidationFailure)


def _parse_content_type(content_type: Optional[str]) -> Tuple[Optional[str], str]:
Expand Down Expand Up @@ -307,12 +308,19 @@ def decipher_response(status_code: int, headers: Mapping[str, str],
raise RateLimitExceeded(rate_limit, message)
elif status_code == 422:
errors = data.get("errors", None)
exc_type = InvalidField
if errors:
fields = ", ".join(repr(e["field"]) for e in errors)
message = f"{message} for {fields}"
if any(e['code'] in ['missing', 'missing_field', 'invalid', 'already_exists']
for e in errors):
error_context = ", ".join(repr(e.get("field")) for e in errors)
message = f"{message} for {error_context}"
else:
exc_type = ValidationError
error_context = ", ".join(repr(e.get("message")) for e in errors)
message = f"{message}: {error_context}"
else:
message = data["message"]
raise InvalidField(errors, message)
raise exc_type(errors, message)
elif status_code >= 300:
exc_type = RedirectionException
else:
Expand Down
17 changes: 16 additions & 1 deletion gidgethub/test/test_sansio.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import pytest

from .. import (BadRequest, GitHubBroken, HTTPException, InvalidField,
RateLimitExceeded, RedirectionException, ValidationFailure)
RateLimitExceeded, RedirectionException, ValidationError,
ValidationFailure)
from .. import sansio


Expand Down Expand Up @@ -320,6 +321,20 @@ def test_422(self):
assert exc_info.value.status_code == http.HTTPStatus(status_code)
assert str(exc_info.value) == "it went bad for 'title'"

def test_422_custom_code(self):
status_code = 422
errors = [
{"resource": "PullRequest", "code": "custom",
"message": "A pull request already exists for foo:1."},
]
body = json.dumps({"message": "it went bad", "errors": errors})
body = body.encode("utf-8")
headers = {"content-type": "application/json; charset=utf-8"}
with pytest.raises(ValidationError) as exc_info:
sansio.decipher_response(status_code, headers, body)
assert exc_info.value.status_code == http.HTTPStatus(status_code)
assert str(exc_info.value) == "it went bad: 'A pull request already exists for foo:1.'"

def test_422_no_errors_object(self):
status_code = 422
body = json.dumps({"message": "Reference does not exist",
Expand Down

0 comments on commit ddc715a

Please sign in to comment.