diff --git a/github/github.go b/github/github.go index 07b08ea27bf..90c88f0dc82 100644 --- a/github/github.go +++ b/github/github.go @@ -1522,6 +1522,35 @@ type ErrorResponse struct { DocumentationURL string `json:"documentation_url,omitempty"` } +// UnmarshalJSON implements the json.Unmarshaler interface. +func (r *ErrorResponse) UnmarshalJSON(data []byte) error { + type aliasErrorResponse ErrorResponse // avoid infinite recursion by using type alias. + response := struct { + Errors json.RawMessage `json:"errors"` + *aliasErrorResponse + }{ + aliasErrorResponse: (*aliasErrorResponse)(r), + } + + if err := json.Unmarshal(data, &response); err != nil { + return err + } + if len(response.Errors) == 0 || string(response.Errors) == "null" { + return nil + } + if err := json.Unmarshal(response.Errors, &r.Errors); err == nil { + return nil + } + + var message string + if err := json.Unmarshal(response.Errors, &message); err != nil { + return err + } + //nolint:sliceofpointers + r.Errors = []Error{{Message: message}} + return nil +} + // ErrorBlock contains a further explanation for the reason of an error. // See https://developer.github.com/changes/2016-03-17-the-451-status-code-is-now-supported/ // for more information. diff --git a/github/github_test.go b/github/github_test.go index ae585cede4d..d381d746f44 100644 --- a/github/github_test.go +++ b/github/github_test.go @@ -4139,6 +4139,32 @@ func TestCheckResponse_unexpectedErrorStructure(t *testing.T) { } } +func TestCheckResponse_errorString(t *testing.T) { + t.Parallel() + httpBody := `{"message":"Forbidden","errors":"Repository level self-hosted runners are disabled on this repository","documentation_url":"https://docs.github.com/rest/actions/self-hosted-runners#create-a-registration-token-for-a-repository","status":"403"}` + res := &http.Response{ + Request: &http.Request{}, + StatusCode: http.StatusForbidden, + Body: io.NopCloser(strings.NewReader(httpBody)), + } + var err *ErrorResponse + errors.As(CheckResponse(res), &err) + + if err == nil { + t.Fatal("Expected error response.") + } + + want := &ErrorResponse{ + Response: res, + Message: "Forbidden", + Errors: []Error{{Message: "Repository level self-hosted runners are disabled on this repository"}}, + DocumentationURL: "https://docs.github.com/rest/actions/self-hosted-runners#create-a-registration-token-for-a-repository", + } + if !errors.Is(err, want) { + t.Errorf("Error = %#v, want %#v", err, want) + } +} + // TestCheckResponse_LargeBodyTruncated verifies that CheckResponse reads at // most maxErrorBodySize bytes from an error response body, so that a // malicious or buggy server cannot cause the client to allocate unbounded