Repository: gidgethub/gidgethub

Issue:
Traceback on 422 error response
I get the following traceback on a GitHub api response, which looks like it was a 422 status code.  I'm using version 5.3.0 of gidgethub.

```
2023-07-07T04:50:40.185186+00:00 app[web.1]:   File "/app/precaution/review.py", line 30, in create
2023-07-07T04:50:40.185186+00:00 app[web.1]:     response = await self.gh.post(
2023-07-07T04:50:40.185187+00:00 app[web.1]:                ^^^^^^^^^^^^^^^^^^^
2023-07-07T04:50:40.185205+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.11/site-packages/gidgethub/abc.py", line 225, in post
2023-07-07T04:50:40.185205+00:00 app[web.1]:     data, _, _ = await self._make_request(
2023-07-07T04:50:40.185206+00:00 app[web.1]:                  ^^^^^^^^^^^^^^^^^^^^^^^^^
2023-07-07T04:50:40.185248+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.11/site-packages/gidgethub/abc.py", line 121, in _make_request
2023-07-07T04:50:40.185249+00:00 app[web.1]:     data, self.rate_limit, more = sansio.decipher_response(*response)
2023-07-07T04:50:40.185249+00:00 app[web.1]:                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2023-07-07T04:50:40.185268+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.11/site-packages/gidgethub/sansio.py", line 352, in decipher_response
2023-07-07T04:50:40.185268+00:00 app[web.1]:     if any(
2023-07-07T04:50:40.185269+00:00 app[web.1]:        ^^^^
2023-07-07T04:50:40.185287+00:00 app[web.1]:   File "/app/.heroku/python/lib/python3.11/site-packages/gidgethub/sansio.py", line 353, in <genexpr>
2023-07-07T04:50:40.185287+00:00 app[web.1]:     e["code"]
2023-07-07T04:50:40.185287+00:00 app[web.1]:     ~^^^^^^^^
2023-07-07T04:50:40.185334+00:00 app[web.1]: TypeError: string indices must be integers, not 'str'
```

Discussion:
Copilot: @brettcannon 👋 This repository doesn't have [Copilot instructions](https://docs.github.com/enterprise-cloud@latest/copilot/how-tos/configure-custom-instructions/add-repository-instructions?tool=webui). With Copilot instructions, I can understand the repository better, work faster and produce higher quality PRs.

I can generate a .github/copilot-instructions.md file for you automatically. Click [here](https://github.com/gidgethub/gidgethub/issues/new?title=✨Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) to open a pre-filled issue and assign it to me. I'll write the instructions, and then tag you for review.

brettcannon: @copilot I diagnosed the issue wrong; it was because the "errors" key contained a string. Go back to the issue, find the example in https://github.com/gidgethub/gidgethub/issues/200#issuecomment-2981901748 , make a test for it, and then update the PR to pass that test.
Copilot: 
> @copilot I diagnosed the issue wrong; it was because the "errors" key contained a string. Go back to the issue, find the example in https://github.com/gidgethub/gidgethub/issues/200#issuecomment-29819...

You're absolutely right! I misdiagnosed the issue initially. The real problem is that GitHub API can return `"errors"` as a string instead of an array of objects. I've now:

1. Added a test for the exact case from comment #2981901748 where `errors` is a string
2. Fixed the code to handle both string and list `errors` fields  
3. Verified all existing tests still pass

The fix is in commit e66c373.
Copilot: 
> @copilot don't worry about `e` not having a "code" key; that was my bad for worrying about it. Undo changes in the PR related to that and only keep the stuff related to "errors" being a string.

Done! I've removed all the changes related to handling missing "code" fields and kept only the string errors handling. The changes include:

- Reverted `e.get("code")` back to `e["code"]` in the any() check
- Removed the `test_422_errors_missing_code_field` test 
- Kept the string errors handling and `test_422_errors_as_string` test

All 422-related tests pass. Commit: a7b2ef5
brettcannon: @copilot please address the review comments and the lint failure.

Fix (patch):
diff --git a/gidgethub/sansio.py b/gidgethub/sansio.py
index 414daac..95830f6 100644
--- a/gidgethub/sansio.py
+++ b/gidgethub/sansio.py
@@ -348,7 +348,10 @@ def decipher_response(
                     raise BadRequestUnknownError(data)
                 exc_type = InvalidField
                 if errors:
-                    if any(
+                    if isinstance(errors, str):
+                        exc_type = ValidationError
+                        message = f"{message}: {errors}"
+                    elif any(
                         e["code"]
                         in ["missing", "missing_field", "invalid", "already_exists"]
                         for e in errors


Test changes:
diff --git a/tests/test_sansio.py b/tests/test_sansio.py
index 5492de3..1139adb 100644
--- a/tests/test_sansio.py
+++ b/tests/test_sansio.py
@@ -366,6 +366,27 @@ class TestDecipherResponse:
             == "it went bad: 'A pull request already exists for foo:1.'"
         )
 
+    def test_422_errors_as_string(self):
+        """Test 422 response where 'errors' field is a string instead of list of objects."""
+        status_code = 422
+        body = json.dumps(
+            {
+                "message": "Validation Failed",
+                "errors": "Validation failed: This SHA and context has reached the maximum number of statuses.",
+                "documentation_url": "https://docs.github.com/rest/commits/statuses#create-a-commit-status",
+                "status": "422",
+            }
+        )
+        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)
+            == "Validation Failed: Validation failed: This SHA and context has reached the maximum number of statuses."
+        )
+
     def test_422_no_errors_object(self):
         status_code = 422
         body = json.dumps(