Skip to content

Commit 35072aa

Browse files
committed
fix: restjson error deserialization when no body is present
1 parent 3576b9b commit 35072aa

File tree

3 files changed

+286
-156
lines changed

3 files changed

+286
-156
lines changed

CHANGELOG_PENDING.md

+2
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
### SDK Enhancements
44

55
### SDK Bugs
6+
* `restjson`: Correct failure to deserialize errors.
7+
* Deserialize generic error information when no response body is present.

private/protocol/restjson/unmarshal_error.go

+78-57
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package restjson
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"io"
67
"io/ioutil"
78
"net/http"
@@ -40,54 +41,30 @@ func (u *UnmarshalTypedError) UnmarshalError(
4041
resp *http.Response,
4142
respMeta protocol.ResponseMetadata,
4243
) (error, error) {
43-
44-
code := resp.Header.Get(errorTypeHeader)
45-
msg := resp.Header.Get(errorMessageHeader)
46-
47-
body := resp.Body
48-
if len(code) == 0 || len(msg) == 0 {
49-
// If unable to get code from HTTP headers have to parse JSON message
50-
// to determine what kind of exception this will be.
51-
var buf bytes.Buffer
52-
var jsonErr jsonErrorResponse
53-
teeReader := io.TeeReader(resp.Body, &buf)
54-
err := jsonutil.UnmarshalJSONError(&jsonErr, teeReader)
55-
if err != nil {
56-
return nil, err
57-
}
58-
59-
body = ioutil.NopCloser(&buf)
60-
if len(code) == 0 {
61-
code = jsonErr.Code
62-
}
63-
msg = jsonErr.Message
44+
code, msg, err := unmarshalErrorInfo(resp)
45+
if err != nil {
46+
return nil, err
6447
}
6548

66-
// If code has colon separators remove them so can compare against modeled
67-
// exception names.
68-
code = strings.SplitN(code, ":", 2)[0]
69-
70-
if fn, ok := u.exceptions[code]; ok {
71-
// If exception code is know, use associated constructor to get a value
72-
// for the exception that the JSON body can be unmarshaled into.
73-
v := fn(respMeta)
74-
if err := jsonutil.UnmarshalJSONCaseInsensitive(v, body); err != nil {
75-
return nil, err
76-
}
49+
fn, ok := u.exceptions[code]
50+
if !ok {
51+
return awserr.NewRequestFailure(
52+
awserr.New(code, msg, nil),
53+
respMeta.StatusCode,
54+
respMeta.RequestID,
55+
), nil
56+
}
7757

78-
if err := rest.UnmarshalResponse(resp, v, true); err != nil {
79-
return nil, err
80-
}
58+
v := fn(respMeta)
59+
if err := jsonutil.UnmarshalJSONCaseInsensitive(v, resp.Body); err != nil {
60+
return nil, err
61+
}
8162

82-
return v, nil
63+
if err := rest.UnmarshalResponse(resp, v, true); err != nil {
64+
return nil, err
8365
}
8466

85-
// fallback to unmodeled generic exceptions
86-
return awserr.NewRequestFailure(
87-
awserr.New(code, msg, nil),
88-
respMeta.StatusCode,
89-
respMeta.RequestID,
90-
), nil
67+
return v, nil
9168
}
9269

9370
// UnmarshalErrorHandler is a named request handler for unmarshaling restjson
@@ -101,36 +78,80 @@ var UnmarshalErrorHandler = request.NamedHandler{
10178
func UnmarshalError(r *request.Request) {
10279
defer r.HTTPResponse.Body.Close()
10380

104-
var jsonErr jsonErrorResponse
105-
err := jsonutil.UnmarshalJSONError(&jsonErr, r.HTTPResponse.Body)
81+
code, msg, err := unmarshalErrorInfo(r.HTTPResponse)
10682
if err != nil {
10783
r.Error = awserr.NewRequestFailure(
108-
awserr.New(request.ErrCodeSerialization,
109-
"failed to unmarshal response error", err),
84+
awserr.New(request.ErrCodeSerialization, "failed to unmarshal response error", err),
11085
r.HTTPResponse.StatusCode,
11186
r.RequestID,
11287
)
11388
return
11489
}
11590

116-
code := r.HTTPResponse.Header.Get(errorTypeHeader)
117-
if code == "" {
118-
code = jsonErr.Code
119-
}
120-
msg := r.HTTPResponse.Header.Get(errorMessageHeader)
121-
if msg == "" {
122-
msg = jsonErr.Message
123-
}
124-
125-
code = strings.SplitN(code, ":", 2)[0]
12691
r.Error = awserr.NewRequestFailure(
127-
awserr.New(code, jsonErr.Message, nil),
92+
awserr.New(code, msg, nil),
12893
r.HTTPResponse.StatusCode,
12994
r.RequestID,
13095
)
13196
}
13297

13398
type jsonErrorResponse struct {
99+
Type string `json:"__type"`
134100
Code string `json:"code"`
135101
Message string `json:"message"`
136102
}
103+
104+
func (j *jsonErrorResponse) SanitizedCode() string {
105+
code := j.Code
106+
if len(j.Type) > 0 {
107+
code = j.Type
108+
}
109+
return sanitizeCode(code)
110+
}
111+
112+
// Remove superfluous components from a restJson error code.
113+
// - If a : character is present, then take only the contents before the
114+
// first : character in the value.
115+
// - If a # character is present, then take only the contents after the first
116+
// # character in the value.
117+
//
118+
// All of the following error values resolve to FooError:
119+
// - FooError
120+
// - FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/
121+
// - aws.protocoltests.restjson#FooError
122+
// - aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/
123+
func sanitizeCode(code string) string {
124+
noColon := strings.SplitN(code, ":", 2)[0]
125+
hashSplit := strings.SplitN(noColon, "#", 2)
126+
return hashSplit[len(hashSplit)-1]
127+
}
128+
129+
// attempt to garner error details from the response, preferring header values
130+
// when present
131+
func unmarshalErrorInfo(resp *http.Response) (code string, msg string, err error) {
132+
code = sanitizeCode(resp.Header.Get(errorTypeHeader))
133+
msg = resp.Header.Get(errorMessageHeader)
134+
if len(code) > 0 && len(msg) > 0 {
135+
return
136+
}
137+
138+
// a modeled error will have to be re-deserialized later, so the body must
139+
// be preserved
140+
var buf bytes.Buffer
141+
tee := io.TeeReader(resp.Body, &buf)
142+
defer func() { resp.Body = ioutil.NopCloser(&buf) }()
143+
144+
var jsonErr jsonErrorResponse
145+
if decodeErr := json.NewDecoder(tee).Decode(&jsonErr); decodeErr != nil && decodeErr != io.EOF {
146+
err = awserr.NewUnmarshalError(decodeErr, "failed to decode response body", buf.Bytes())
147+
return
148+
}
149+
150+
if len(code) == 0 {
151+
code = jsonErr.SanitizedCode()
152+
}
153+
if len(msg) == 0 {
154+
msg = jsonErr.Message
155+
}
156+
return
157+
}

0 commit comments

Comments
 (0)