Skip to content
Merged
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
29 changes: 27 additions & 2 deletions swift.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,26 @@ func newErrorf(StatusCode int, Text string, Parameters ...interface{}) *Error {
return newError(StatusCode, fmt.Sprintf(Text, Parameters...))
}

type ErrorWithBody struct {
Err *Error
ResponseBody string // Original response body
}

// Error satisfies the error interface.
func (e *ErrorWithBody) Error() string {
if e.Err == nil {
return ""
}
if e.ResponseBody == "" {
return e.Err.Error()
}
return fmt.Sprintf("%s: %s", e.Err.Error(), e.ResponseBody)
}

func (e *ErrorWithBody) Unwrap() error {
return e.Err
}

// errorMap defines http error codes to error mappings.
type errorMap map[int]error

Expand Down Expand Up @@ -421,7 +441,7 @@ func appendResponseBodyToError(resp *http.Response, err error) error {
buf := make([]byte, respBodyErrSizeLimit)
limitedReader := io.LimitReader(resp.Body, respBodyErrSizeLimit)
n, readErr := limitedReader.Read(buf)
if readErr != nil || n == 0 {
if (readErr != nil && !errors.Is(readErr, io.EOF)) || n == 0 {
return err
}

Expand All @@ -430,7 +450,12 @@ func appendResponseBodyToError(resp *http.Response, err error) error {
return err
}

return fmt.Errorf("%w: %s", err, trimmed)
assertedErr, ok := err.(*Error)
if !ok {
return err
}

return &ErrorWithBody{Err: assertedErr, ResponseBody: trimmed}
}

// readHeaders returns a Headers object from the http.Response.
Expand Down
42 changes: 41 additions & 1 deletion swift_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,22 @@ func compareMaps(t *testing.T, a, b map[string]string) {
}
}

// eofReader is used for testing where a reader might return io.EOF on the first read.
type eofReader struct {
reader io.Reader
}

func (r *eofReader) Read(b []byte) (int, error) {
n, err := r.reader.Read(b)
if err != nil {
return n, err
}
if n == 0 || n < len(b) {
return n, io.EOF
}
return n, nil
}

func TestInternalError(t *testing.T) {
e := newError(404, "Not Found!")
if e.StatusCode != 404 || e.Text != "Not Found!" {
Expand Down Expand Up @@ -349,6 +365,20 @@ func TestInternalParseHeadersWithErrorMessageInBody(t *testing.T) {
errMap: objectErrorMap,
expected: fmt.Sprintf("%s: %s", "Bad Request", strings.Repeat("a", 1024)),
},
{
name: "Object error with reader that returns EOF right away",
resp: &http.Response{
StatusCode: 400,
Header: http.Header{
"Content-Type": []string{"text/plain"},
},
ContentLength: 15,
Body: io.NopCloser(&eofReader{reader: strings.NewReader("Body message")}),
Status: "Status message",
},
errMap: objectErrorMap,
expected: "Bad Request: Body message",
},
}

for _, tc := range testCases {
Expand All @@ -360,10 +390,20 @@ func TestInternalParseHeadersWithErrorMessageInBody(t *testing.T) {
if err.Error() != tc.expected {
t.Errorf("Expected error %q, got %q", tc.expected, err.Error())
}
if tc.errMap != nil {
expectedInnerErr := tc.errMap[tc.resp.StatusCode]
if !errors.Is(err, expectedInnerErr) {
assertedErr, ok := err.(*ErrorWithBody)
if ok {
t.Errorf("Failed to unwrap ErrorWithBody: expected inner error: %q, got %q", expectedInnerErr.Error(), assertedErr.Err.Error())
} else {
t.Errorf("Failed to unwrap ErrorWithBody: failed to assert error")
}
}
}
})
}
}

func TestInternalReadHeaders(t *testing.T) {
resp := &http.Response{Header: http.Header{}}
compareMaps(t, readHeaders(resp), Headers{})
Expand Down