Skip to content

Commit 8699613

Browse files
authored
Prevent panic in ResponseError.Error() (Azure#21839)
* Prevent panic in ResponseError.Error() If RawResponse is nil (unit test scenarios), print "nil RawResponse" instead of panicing. * handle nil Request in *http.Response tweak string for missing RawResponse
1 parent 7084c2a commit 8699613

File tree

3 files changed

+49
-18
lines changed

3 files changed

+49
-18
lines changed

sdk/azcore/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* Fixed an issue that could cause an HTTP/2 request to hang when the TCP connection becomes unresponsive.
1616
* Block key and SAS authentication for non TLS protected endpoints.
1717
* Passing a `nil` credential value will no longer cause a panic. Instead, the authentication is skipped.
18+
* Calling `Error` on a zero-value `azcore.ResponseError` will no longer panic.
1819

1920
### Other Changes
2021

sdk/azcore/internal/exported/response_error.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -113,33 +113,45 @@ type ResponseError struct {
113113
// Error implements the error interface for type ResponseError.
114114
// Note that the message contents are not contractual and can change over time.
115115
func (e *ResponseError) Error() string {
116+
const separator = "--------------------------------------------------------------------------------"
116117
// write the request method and URL with response status code
117118
msg := &bytes.Buffer{}
118-
fmt.Fprintf(msg, "%s %s://%s%s\n", e.RawResponse.Request.Method, e.RawResponse.Request.URL.Scheme, e.RawResponse.Request.URL.Host, e.RawResponse.Request.URL.Path)
119-
fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
120-
fmt.Fprintf(msg, "RESPONSE %d: %s\n", e.RawResponse.StatusCode, e.RawResponse.Status)
119+
if e.RawResponse != nil {
120+
if e.RawResponse.Request != nil {
121+
fmt.Fprintf(msg, "%s %s://%s%s\n", e.RawResponse.Request.Method, e.RawResponse.Request.URL.Scheme, e.RawResponse.Request.URL.Host, e.RawResponse.Request.URL.Path)
122+
} else {
123+
fmt.Fprintln(msg, "Request information not available")
124+
}
125+
fmt.Fprintln(msg, separator)
126+
fmt.Fprintf(msg, "RESPONSE %d: %s\n", e.RawResponse.StatusCode, e.RawResponse.Status)
127+
} else {
128+
fmt.Fprintln(msg, "Missing RawResponse")
129+
fmt.Fprintln(msg, separator)
130+
}
121131
if e.ErrorCode != "" {
122132
fmt.Fprintf(msg, "ERROR CODE: %s\n", e.ErrorCode)
123133
} else {
124134
fmt.Fprintln(msg, "ERROR CODE UNAVAILABLE")
125135
}
126-
fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
127-
body, err := exported.Payload(e.RawResponse, nil)
128-
if err != nil {
129-
// this really shouldn't fail at this point as the response
130-
// body is already cached (it was read in NewResponseError)
131-
fmt.Fprintf(msg, "Error reading response body: %v", err)
132-
} else if len(body) > 0 {
133-
if err := json.Indent(msg, body, "", " "); err != nil {
134-
// failed to pretty-print so just dump it verbatim
135-
fmt.Fprint(msg, string(body))
136+
if e.RawResponse != nil {
137+
fmt.Fprintln(msg, separator)
138+
body, err := exported.Payload(e.RawResponse, nil)
139+
if err != nil {
140+
// this really shouldn't fail at this point as the response
141+
// body is already cached (it was read in NewResponseError)
142+
fmt.Fprintf(msg, "Error reading response body: %v", err)
143+
} else if len(body) > 0 {
144+
if err := json.Indent(msg, body, "", " "); err != nil {
145+
// failed to pretty-print so just dump it verbatim
146+
fmt.Fprint(msg, string(body))
147+
}
148+
// the standard library doesn't have a pretty-printer for XML
149+
fmt.Fprintln(msg)
150+
} else {
151+
fmt.Fprintln(msg, "Response contained no body")
136152
}
137-
// the standard library doesn't have a pretty-printer for XML
138-
fmt.Fprintln(msg)
139-
} else {
140-
fmt.Fprintln(msg, "Response contained no body")
141153
}
142-
fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
154+
fmt.Fprintln(msg, separator)
143155

144156
return msg.String()
145157
}

sdk/azcore/internal/exported/response_error_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"testing"
1616

1717
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
18+
"github.com/stretchr/testify/require"
1819
)
1920

2021
func TestNewResponseErrorNoBodyNoErrorCode(t *testing.T) {
@@ -450,3 +451,20 @@ func TestExtractErrorCodeFromJSON(t *testing.T) {
450451
t.Fatalf("expected %s got %s", "ResourceNotFound", code)
451452
}
452453
}
454+
455+
func TestNilRawResponse(t *testing.T) {
456+
const expected = "Missing RawResponse\n--------------------------------------------------------------------------------\nERROR CODE UNAVAILABLE\n--------------------------------------------------------------------------------\n"
457+
require.EqualValues(t, expected, (&ResponseError{}).Error())
458+
}
459+
460+
func TestNilRequestInRawResponse(t *testing.T) {
461+
const expected = "Request information not available\n--------------------------------------------------------------------------------\nRESPONSE 400: status\nERROR CODE UNAVAILABLE\n--------------------------------------------------------------------------------\nResponse contained no body\n--------------------------------------------------------------------------------\n"
462+
respErr := &ResponseError{
463+
RawResponse: &http.Response{
464+
Body: http.NoBody,
465+
Status: "status",
466+
StatusCode: http.StatusBadRequest,
467+
},
468+
}
469+
require.EqualValues(t, expected, respErr.Error())
470+
}

0 commit comments

Comments
 (0)