-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
unmarshal_error.go
160 lines (140 loc) · 5.18 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
158
159
160
package jsonrpc
import (
"bytes"
"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"
)
const (
awsQueryError = "x-amzn-query-error"
// A valid header example - "x-amzn-query-error": "<QueryErrorCode>;<ErrorType>"
awsQueryErrorPartsCount = 2
)
// UnmarshalTypedError provides unmarshaling errors API response errors
// for both typed and untyped errors.
type UnmarshalTypedError struct {
exceptions map[string]func(protocol.ResponseMetadata) error
queryExceptions map[string]func(protocol.ResponseMetadata, string) 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,
queryExceptions: map[string]func(protocol.ResponseMetadata, string) error{},
}
}
// NewUnmarshalTypedErrorWithOptions works similar to NewUnmarshalTypedError applying options to the UnmarshalTypedError
// before returning it
func NewUnmarshalTypedErrorWithOptions(exceptions map[string]func(protocol.ResponseMetadata) error, optFns ...func(*UnmarshalTypedError)) *UnmarshalTypedError {
unmarshaledError := NewUnmarshalTypedError(exceptions)
for _, fn := range optFns {
fn(unmarshaledError)
}
return unmarshaledError
}
// WithQueryCompatibility is a helper function to construct a functional option for use with NewUnmarshalTypedErrorWithOptions.
// The queryExceptions given act as an override for unmarshalling errors when query compatible error codes are found.
// See also [awsQueryCompatible trait]
//
// [awsQueryCompatible trait]: https://smithy.io/2.0/aws/protocols/aws-query-protocol.html#aws-protocols-awsquerycompatible-trait
func WithQueryCompatibility(queryExceptions map[string]func(protocol.ResponseMetadata, string) error) func(*UnmarshalTypedError) {
return func(typedError *UnmarshalTypedError) {
typedError.queryExceptions = queryExceptions
}
}
// 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) {
var buf bytes.Buffer
var jsonErr jsonErrorResponse
teeReader := io.TeeReader(resp.Body, &buf)
err := jsonutil.UnmarshalJSONError(&jsonErr, teeReader)
if err != nil {
return nil, err
}
body := ioutil.NopCloser(&buf)
// Code may be separated by hash(#), with the last element being the code
// used by the SDK.
codeParts := strings.SplitN(jsonErr.Code, "#", 2)
code := codeParts[len(codeParts)-1]
msg := jsonErr.Message
queryCodeParts := queryCodeParts(resp, u)
if fn, ok := u.exceptions[code]; ok {
// If query-compatible exceptions are found and query-error-header is found,
// then use associated constructor to get exception with query error code.
//
// If exception code is known, use associated constructor to get a value
// for the exception that the JSON body can be unmarshaled into.
var v error
queryErrFn, queryExceptionsFound := u.queryExceptions[code]
if len(queryCodeParts) == awsQueryErrorPartsCount && queryExceptionsFound {
v = queryErrFn(respMeta, queryCodeParts[0])
} else {
v = fn(respMeta)
}
err := jsonutil.UnmarshalJSONCaseInsensitive(v, body)
if err != nil {
return nil, err
}
return v, nil
}
if len(queryCodeParts) == awsQueryErrorPartsCount && len(u.queryExceptions) > 0 {
code = queryCodeParts[0]
}
// fallback to unmodeled generic exceptions
return awserr.NewRequestFailure(
awserr.New(code, msg, nil),
respMeta.StatusCode,
respMeta.RequestID,
), nil
}
// A valid header example - "x-amzn-query-error": "<QueryErrorCode>;<ErrorType>"
func queryCodeParts(resp *http.Response, u *UnmarshalTypedError) []string {
queryCodeHeader := resp.Header.Get(awsQueryError)
var queryCodeParts []string
if queryCodeHeader != "" && len(u.queryExceptions) > 0 {
queryCodeParts = strings.Split(queryCodeHeader, ";")
}
return queryCodeParts
}
// UnmarshalErrorHandler is a named request handler for unmarshaling jsonrpc
// protocol request errors
var UnmarshalErrorHandler = request.NamedHandler{
Name: "awssdk.jsonrpc.UnmarshalError",
Fn: UnmarshalError,
}
// UnmarshalError unmarshals an error response for a JSON RPC service.
func UnmarshalError(req *request.Request) {
defer req.HTTPResponse.Body.Close()
var jsonErr jsonErrorResponse
err := jsonutil.UnmarshalJSONError(&jsonErr, req.HTTPResponse.Body)
if err != nil {
req.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization,
"failed to unmarshal error message", err),
req.HTTPResponse.StatusCode,
req.RequestID,
)
return
}
codes := strings.SplitN(jsonErr.Code, "#", 2)
req.Error = awserr.NewRequestFailure(
awserr.New(codes[len(codes)-1], jsonErr.Message, nil),
req.HTTPResponse.StatusCode,
req.RequestID,
)
}
type jsonErrorResponse struct {
Code string `json:"__type"`
Message string `json:"message"`
}