Skip to content

Commit d0931eb

Browse files
authored
aws: add de/serialization typed error to the SDK (#514)
* 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.
1 parent 2bc00eb commit d0931eb

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

aws/request.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,33 @@ const (
2424
ErrCodeRead = "ReadError"
2525
)
2626

27+
// SerializationError provides a generic request serialization error
28+
type SerializationError struct {
29+
Err error // original error
30+
}
31+
32+
// Error returns a formatted error for SerializationError
33+
func (e *SerializationError) Error() string {
34+
return fmt.Sprintf("serialization failed: %v", e.Err)
35+
}
36+
37+
// Unwrap returns the underlying Error in DeserializationError
38+
func (e *SerializationError) Unwrap() error { return e.Err }
39+
40+
// DeserializationError provides a HTTP transport specific
41+
// request deserialization error
42+
type DeserializationError struct {
43+
Err error // original error
44+
}
45+
46+
// Error returns a formatted error for DeserializationError
47+
func (e *DeserializationError) Error() string {
48+
return fmt.Sprintf("deserialization failed, %v", e.Err)
49+
}
50+
51+
// Unwrap returns the underlying Error in DeserializationError
52+
func (e *DeserializationError) Unwrap() error { return e.Err }
53+
2754
// RequestSendError provides a generic request transport error.
2855
type RequestSendError struct {
2956
Response interface{}

aws/typed_error_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package aws_test
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"io/ioutil"
9+
"net/http"
10+
"strings"
11+
"testing"
12+
13+
"github.com/aws/aws-sdk-go-v2/aws"
14+
"github.com/aws/aws-sdk-go-v2/internal/sdkio"
15+
"github.com/aws/aws-sdk-go-v2/private/protocol/json/jsonutil"
16+
)
17+
18+
type mockOP struct {
19+
Message *string `locationName:"message" type:"string"`
20+
}
21+
22+
type mockUnmarshaler struct {
23+
output *mockOP
24+
}
25+
26+
const correctResponse = `{"message": "Hello"}`
27+
const responseWithMissingDelimiter = `"message": "Hello"}`
28+
const invalidResponse = `{"message": true}`
29+
30+
func TestHTTPDeserializationError(t *testing.T) {
31+
cases := map[string]struct {
32+
responseBody string
33+
responseStatus int
34+
requestID string
35+
errorIsSet bool
36+
}{
37+
"No error": {
38+
responseBody: correctResponse,
39+
requestID: "mockReqID",
40+
responseStatus: 200,
41+
},
42+
"Missing delimiter": {
43+
responseBody: responseWithMissingDelimiter,
44+
errorIsSet: true,
45+
requestID: "mockReqID",
46+
responseStatus: 200,
47+
},
48+
"Invalid response": {
49+
responseBody: invalidResponse,
50+
errorIsSet: true,
51+
requestID: "mockReqID",
52+
responseStatus: 200,
53+
},
54+
}
55+
56+
for name, c := range cases {
57+
t.Run(name, func(t *testing.T) {
58+
r := &aws.Request{
59+
HTTPResponse: &http.Response{
60+
StatusCode: 200,
61+
Body: ioutil.NopCloser(bytes.NewReader([]byte(c.responseBody))),
62+
},
63+
RequestID: "mockReqID",
64+
}
65+
66+
u := mockUnmarshaler{
67+
output: &mockOP{},
68+
}
69+
// unmarshal request response
70+
u.unmarshalOperation(r)
71+
72+
if c.errorIsSet {
73+
if r.Error == nil {
74+
t.Fatal("Expected error, got none")
75+
}
76+
if r.Error == nil {
77+
t.Fatal("Expected error, got none")
78+
}
79+
var e *aws.DeserializationError
80+
if errors.As(r.Error, &e) {
81+
if e, a := c.responseBody, e.Unwrap().Error(); !strings.Contains(a, e) {
82+
t.Fatalf("Expected response body to contain %v, got %v", e, a)
83+
}
84+
} else {
85+
t.Fatalf("Expected error to be of type %T, got %T", e, r.Error)
86+
}
87+
} else {
88+
if r.Error != nil {
89+
t.Fatalf("Expected no error, got %v", r.Error)
90+
}
91+
}
92+
})
93+
}
94+
}
95+
96+
// unmarshal operation unmarshal's request response
97+
func (u *mockUnmarshaler) unmarshalOperation(r *aws.Request) {
98+
b := make([]byte, 1024)
99+
ringbuffer := sdkio.NewRingBuffer(b)
100+
// wraps ring buffer around the response body
101+
body := io.TeeReader(r.HTTPResponse.Body, ringbuffer)
102+
103+
// If unmarshaling function returns an error, it is a deserialization error
104+
if err := jsonutil.UnmarshalJSON(u.output, body); err != nil {
105+
snapshot := make([]byte, 1024)
106+
ringbuffer.Read(snapshot)
107+
r.Error = &aws.DeserializationError{
108+
Err: fmt.Errorf("unable to deserialize Json payload: %w.\n"+
109+
"Here's a snapshot of response being deserialized: %s", err, snapshot),
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)