@@ -2,6 +2,7 @@ package restjson
2
2
3
3
import (
4
4
"bytes"
5
+ "encoding/json"
5
6
"io"
6
7
"io/ioutil"
7
8
"net/http"
@@ -40,54 +41,30 @@ func (u *UnmarshalTypedError) UnmarshalError(
40
41
resp * http.Response ,
41
42
respMeta protocol.ResponseMetadata ,
42
43
) (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
64
47
}
65
48
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
+ }
77
57
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
+ }
81
62
82
- return v , nil
63
+ if err := rest .UnmarshalResponse (resp , v , true ); err != nil {
64
+ return nil , err
83
65
}
84
66
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
91
68
}
92
69
93
70
// UnmarshalErrorHandler is a named request handler for unmarshaling restjson
@@ -101,36 +78,80 @@ var UnmarshalErrorHandler = request.NamedHandler{
101
78
func UnmarshalError (r * request.Request ) {
102
79
defer r .HTTPResponse .Body .Close ()
103
80
104
- var jsonErr jsonErrorResponse
105
- err := jsonutil .UnmarshalJSONError (& jsonErr , r .HTTPResponse .Body )
81
+ code , msg , err := unmarshalErrorInfo (r .HTTPResponse )
106
82
if err != nil {
107
83
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 ),
110
85
r .HTTPResponse .StatusCode ,
111
86
r .RequestID ,
112
87
)
113
88
return
114
89
}
115
90
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 ]
126
91
r .Error = awserr .NewRequestFailure (
127
- awserr .New (code , jsonErr . Message , nil ),
92
+ awserr .New (code , msg , nil ),
128
93
r .HTTPResponse .StatusCode ,
129
94
r .RequestID ,
130
95
)
131
96
}
132
97
133
98
type jsonErrorResponse struct {
99
+ Type string `json:"__type"`
134
100
Code string `json:"code"`
135
101
Message string `json:"message"`
136
102
}
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