Skip to content

Commit

Permalink
Add RequestSendError And CanceledError to HTTP ClientHandler
Browse files Browse the repository at this point in the history
Adds wrapping the HTTP client handler's response with an
RequestSendError for all HTTP request send errors, and CanceledError if
the context passed in to the handler was canceled and the HTTP client
encountered an error.
  • Loading branch information
jasdel committed Sep 21, 2020
1 parent 68b34a8 commit 239cc83
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 2 deletions.
19 changes: 19 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,22 @@ func (e *SerializationError) Error() string {

// Unwrap returns the underlying Error in SerializationError
func (e *SerializationError) Unwrap() error { return e.Err }

// CanceledError is the error that will be returned by an API request that was
// canceled. API operations given a Context may return this error when
// canceled.
type CanceledError struct {
Err error
}

// CanceledError returns true to satisfy interfaces checking for canceled errors.
func (*CanceledError) CanceledError() bool { return true }

// Unwrap returns the underlying error, if there was one.
func (e *CanceledError) Unwrap() error {
return e.Err
}

func (e *CanceledError) Error() string {
return fmt.Sprintf("canceled, %v", e.Err)
}
32 changes: 30 additions & 2 deletions transport/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"net/http/httputil"

smithy "github.com/awslabs/smithy-go"
"github.com/awslabs/smithy-go/middleware"
)

Expand Down Expand Up @@ -49,10 +50,37 @@ func (c ClientHandler) Handle(ctx context.Context, input interface{}) (

resp, err := c.client.Do(req.Build(ctx))
if err != nil {
return nil, metadata, err
err = &RequestSendError{Err: err}

// Override the error with a context canceled error, if that was canceled.
select {
case <-ctx.Done():
err = &smithy.CanceledError{Err: ctx.Err()}
default:
}
}

return &Response{Response: resp}, metadata, nil
return &Response{Response: resp}, metadata, err
}

// RequestSendError provides a generic request transport error.
type RequestSendError struct {
Err error
}

// ConnectionError return that the error is related to not being able to send
// the request.
func (e *RequestSendError) ConnectionError() bool {
return true
}

// Unwrap returns the underlying error, if there was one.
func (e *RequestSendError) Unwrap() error {
return e.Err
}

func (e *RequestSendError) Error() string {
return fmt.Sprintf("request send failed, %v", e.Err)
}

// WrapLogClient logs the client's HTTP request and response of a round tripped
Expand Down
93 changes: 93 additions & 0 deletions transport/http/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package http

import (
"context"
"errors"
"fmt"
"net/http"
"testing"

smithy "github.com/awslabs/smithy-go"
)

func TestClientHandler_Handle(t *testing.T) {
cases := map[string]struct {
Context context.Context
Client ClientDo
ExpectErr func(error) error
}{
"no error": {
Context: context.Background(),
Client: ClientDoFunc(func(*http.Request) (*http.Response, error) {
return &http.Response{}, nil
}),
},
"send error": {
Context: context.Background(),
Client: ClientDoFunc(func(*http.Request) (*http.Response, error) {
return nil, fmt.Errorf("some error")
}),
ExpectErr: func(err error) error {
var sendError *RequestSendError
if !errors.As(err, &sendError) {
return fmt.Errorf("expect error to be %T, %v", sendError, err)
}

var cancelError *smithy.CanceledError
if errors.As(err, &cancelError) {
return fmt.Errorf("expect error to not be %T, %v", cancelError, err)
}

return nil
},
},
"canceled error": {
Context: func() context.Context {
ctx, fn := context.WithCancel(context.Background())
fn()
return ctx
}(),
Client: ClientDoFunc(func(*http.Request) (*http.Response, error) {
return nil, fmt.Errorf("some error")
}),
ExpectErr: func(err error) error {
var sendError *RequestSendError
if errors.As(err, &sendError) {
return fmt.Errorf("expect error to not be %T, %v", sendError, err)
}

var cancelError *smithy.CanceledError
if !errors.As(err, &cancelError) {
return fmt.Errorf("expect error to be %T, %v", cancelError, err)
}

return nil
},
},
}

for name, c := range cases {
t.Run(name, func(t *testing.T) {
handler := NewClientHandler(c.Client)
resp, _, err := handler.Handle(c.Context, NewStackRequest())

if c.ExpectErr != nil {
if err == nil {
t.Fatalf("expect error, got none")
}
if err = c.ExpectErr(err); err != nil {
t.Fatalf("expect error match failed, %v", err)
}
return
}
if err != nil {
t.Fatalf("expect no error, got %v", err)
}

if _, ok := resp.(*Response); !ok {
t.Fatalf("expect Response type, got %T", resp)
}
})
}

}

0 comments on commit 239cc83

Please sign in to comment.