Skip to content

Commit

Permalink
aws: add de/serialization typed error to the SDK (aws#514)
Browse files Browse the repository at this point in the history
* Adds HTTP based de/serialization typed error
* This PR doesn't replace the old usages of Err codes for de/serialization error.
* The serialization/ deserialization error types are generic, and not transport specific. This conforms with layered error design discussed.
  • Loading branch information
skotambkar authored Apr 10, 2020
1 parent 2bc00eb commit d0931eb
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 0 deletions.
27 changes: 27 additions & 0 deletions aws/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,33 @@ const (
ErrCodeRead = "ReadError"
)

// SerializationError provides a generic request serialization error
type SerializationError struct {
Err error // original error
}

// Error returns a formatted error for SerializationError
func (e *SerializationError) Error() string {
return fmt.Sprintf("serialization failed: %v", e.Err)
}

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

// DeserializationError provides a HTTP transport specific
// request deserialization error
type DeserializationError struct {
Err error // original error
}

// Error returns a formatted error for DeserializationError
func (e *DeserializationError) Error() string {
return fmt.Sprintf("deserialization failed, %v", e.Err)
}

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

// RequestSendError provides a generic request transport error.
type RequestSendError struct {
Response interface{}
Expand Down
112 changes: 112 additions & 0 deletions aws/typed_error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package aws_test

import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/internal/sdkio"
"github.com/aws/aws-sdk-go-v2/private/protocol/json/jsonutil"
)

type mockOP struct {
Message *string `locationName:"message" type:"string"`
}

type mockUnmarshaler struct {
output *mockOP
}

const correctResponse = `{"message": "Hello"}`
const responseWithMissingDelimiter = `"message": "Hello"}`
const invalidResponse = `{"message": true}`

func TestHTTPDeserializationError(t *testing.T) {
cases := map[string]struct {
responseBody string
responseStatus int
requestID string
errorIsSet bool
}{
"No error": {
responseBody: correctResponse,
requestID: "mockReqID",
responseStatus: 200,
},
"Missing delimiter": {
responseBody: responseWithMissingDelimiter,
errorIsSet: true,
requestID: "mockReqID",
responseStatus: 200,
},
"Invalid response": {
responseBody: invalidResponse,
errorIsSet: true,
requestID: "mockReqID",
responseStatus: 200,
},
}

for name, c := range cases {
t.Run(name, func(t *testing.T) {
r := &aws.Request{
HTTPResponse: &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader([]byte(c.responseBody))),
},
RequestID: "mockReqID",
}

u := mockUnmarshaler{
output: &mockOP{},
}
// unmarshal request response
u.unmarshalOperation(r)

if c.errorIsSet {
if r.Error == nil {
t.Fatal("Expected error, got none")
}
if r.Error == nil {
t.Fatal("Expected error, got none")
}
var e *aws.DeserializationError
if errors.As(r.Error, &e) {
if e, a := c.responseBody, e.Unwrap().Error(); !strings.Contains(a, e) {
t.Fatalf("Expected response body to contain %v, got %v", e, a)
}
} else {
t.Fatalf("Expected error to be of type %T, got %T", e, r.Error)
}
} else {
if r.Error != nil {
t.Fatalf("Expected no error, got %v", r.Error)
}
}
})
}
}

// unmarshal operation unmarshal's request response
func (u *mockUnmarshaler) unmarshalOperation(r *aws.Request) {
b := make([]byte, 1024)
ringbuffer := sdkio.NewRingBuffer(b)
// wraps ring buffer around the response body
body := io.TeeReader(r.HTTPResponse.Body, ringbuffer)

// If unmarshaling function returns an error, it is a deserialization error
if err := jsonutil.UnmarshalJSON(u.output, body); err != nil {
snapshot := make([]byte, 1024)
ringbuffer.Read(snapshot)
r.Error = &aws.DeserializationError{
Err: fmt.Errorf("unable to deserialize Json payload: %w.\n"+
"Here's a snapshot of response being deserialized: %s", err, snapshot),
}
}
}

0 comments on commit d0931eb

Please sign in to comment.