-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathunmarshal_error.go
157 lines (136 loc) · 4.27 KB
/
unmarshal_error.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package restjson
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol"
"github.com/aws/aws-sdk-go/private/protocol/json/jsonutil"
"github.com/aws/aws-sdk-go/private/protocol/rest"
)
const (
errorTypeHeader = "X-Amzn-Errortype"
errorMessageHeader = "X-Amzn-Errormessage"
)
// UnmarshalTypedError provides unmarshaling errors API response errors
// for both typed and untyped errors.
type UnmarshalTypedError struct {
exceptions map[string]func(protocol.ResponseMetadata) error
}
// NewUnmarshalTypedError returns an UnmarshalTypedError initialized for the
// set of exception names to the error unmarshalers
func NewUnmarshalTypedError(exceptions map[string]func(protocol.ResponseMetadata) error) *UnmarshalTypedError {
return &UnmarshalTypedError{
exceptions: exceptions,
}
}
// UnmarshalError attempts to unmarshal the HTTP response error as a known
// error type. If unable to unmarshal the error type, the generic SDK error
// type will be used.
func (u *UnmarshalTypedError) UnmarshalError(
resp *http.Response,
respMeta protocol.ResponseMetadata,
) (error, error) {
code, msg, err := unmarshalErrorInfo(resp)
if err != nil {
return nil, err
}
fn, ok := u.exceptions[code]
if !ok {
return awserr.NewRequestFailure(
awserr.New(code, msg, nil),
respMeta.StatusCode,
respMeta.RequestID,
), nil
}
v := fn(respMeta)
if err := jsonutil.UnmarshalJSONCaseInsensitive(v, resp.Body); err != nil {
return nil, err
}
if err := rest.UnmarshalResponse(resp, v, true); err != nil {
return nil, err
}
return v, nil
}
// UnmarshalErrorHandler is a named request handler for unmarshaling restjson
// protocol request errors
var UnmarshalErrorHandler = request.NamedHandler{
Name: "awssdk.restjson.UnmarshalError",
Fn: UnmarshalError,
}
// UnmarshalError unmarshals a response error for the REST JSON protocol.
func UnmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close()
code, msg, err := unmarshalErrorInfo(r.HTTPResponse)
if err != nil {
r.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization, "failed to unmarshal response error", err),
r.HTTPResponse.StatusCode,
r.RequestID,
)
return
}
r.Error = awserr.NewRequestFailure(
awserr.New(code, msg, nil),
r.HTTPResponse.StatusCode,
r.RequestID,
)
}
type jsonErrorResponse struct {
Type string `json:"__type"`
Code string `json:"code"`
Message string `json:"message"`
}
func (j *jsonErrorResponse) SanitizedCode() string {
code := j.Code
if len(j.Type) > 0 {
code = j.Type
}
return sanitizeCode(code)
}
// Remove superfluous components from a restJson error code.
// - If a : character is present, then take only the contents before the
// first : character in the value.
// - If a # character is present, then take only the contents after the first
// # character in the value.
//
// All of the following error values resolve to FooError:
// - FooError
// - FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/
// - aws.protocoltests.restjson#FooError
// - aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/
func sanitizeCode(code string) string {
noColon := strings.SplitN(code, ":", 2)[0]
hashSplit := strings.SplitN(noColon, "#", 2)
return hashSplit[len(hashSplit)-1]
}
// attempt to garner error details from the response, preferring header values
// when present
func unmarshalErrorInfo(resp *http.Response) (code string, msg string, err error) {
code = sanitizeCode(resp.Header.Get(errorTypeHeader))
msg = resp.Header.Get(errorMessageHeader)
if len(code) > 0 && len(msg) > 0 {
return
}
// a modeled error will have to be re-deserialized later, so the body must
// be preserved
var buf bytes.Buffer
tee := io.TeeReader(resp.Body, &buf)
defer func() { resp.Body = ioutil.NopCloser(&buf) }()
var jsonErr jsonErrorResponse
if decodeErr := json.NewDecoder(tee).Decode(&jsonErr); decodeErr != nil && decodeErr != io.EOF {
err = awserr.NewUnmarshalError(decodeErr, "failed to decode response body", buf.Bytes())
return
}
if len(code) == 0 {
code = jsonErr.SanitizedCode()
}
if len(msg) == 0 {
msg = jsonErr.Message
}
return
}