1//go:build go1.8 2// +build go1.8 3 4package restjson 5 6import ( 7 "bytes" 8 "encoding/hex" 9 "io/ioutil" 10 "net/http" 11 "reflect" 12 "strings" 13 "testing" 14 15 "github.com/aws/aws-sdk-go/aws" 16 "github.com/aws/aws-sdk-go/aws/awserr" 17 "github.com/aws/aws-sdk-go/aws/request" 18 "github.com/aws/aws-sdk-go/private/protocol" 19) 20 21const unknownErrJSON = `{"code":"UnknownError", "message":"error message", "something":123}` 22const simpleErrJSON = `{"code":"SimpleError", "message":"some message", "foo":123}` 23const simpleNoCodeJSON = `{"message":"some message", "foo":123}` 24 25type SimpleError struct { 26 _ struct{} `type:"structure"` 27 error 28 29 Message2 *string `type:"string" locationName:"message"` 30 Foo *int64 `type:"integer" locationName:"foo"` 31} 32 33const otherErrJSON = `{"code":"OtherError", "message":"some message"}` 34const complexCodeErrJSON = `{"code":"OtherError:foo:bar", "message":"some message"}` 35 36type OtherError struct { 37 _ struct{} `type:"structure"` 38 error 39 40 Message2 *string `type:"string" locationName:"message"` 41} 42 43const complexErrJSON = `{"code":"ComplexError", "message":"some message", "foo": {"bar":"abc123", "baz":123}}` 44 45type ComplexError struct { 46 _ struct{} `type:"structure"` 47 error 48 49 Message2 *string `type:"string" locationName:"message"` 50 Foo *ErrorNested `type:"structure" locationName:"foo"` 51 HeaderVal *string `type:"string" location:"header" locationName:"some-header"` 52 Status *int64 `type:"integer" location:"statusCode"` 53} 54type ErrorNested struct { 55 _ struct{} `type:"structure"` 56 57 Bar *string `type:"string" locationName:"bar"` 58 Baz *int64 `type:"integer" locationName:"baz"` 59} 60 61func TestUnmarshalTypedError(t *testing.T) { 62 63 respMeta := protocol.ResponseMetadata{ 64 StatusCode: 400, 65 RequestID: "abc123", 66 } 67 68 exceptions := map[string]func(protocol.ResponseMetadata) error{ 69 "SimpleError": func(meta protocol.ResponseMetadata) error { 70 return &SimpleError{} 71 }, 72 "OtherError": func(meta protocol.ResponseMetadata) error { 73 return &OtherError{} 74 }, 75 "ComplexError": func(meta protocol.ResponseMetadata) error { 76 return &ComplexError{} 77 }, 78 } 79 80 cases := map[string]struct { 81 Response *http.Response 82 Expect error 83 Err string 84 }{ 85 "simple error": { 86 Response: &http.Response{ 87 Header: http.Header{}, 88 Body: ioutil.NopCloser(strings.NewReader(simpleErrJSON)), 89 }, 90 Expect: &SimpleError{ 91 Message2: aws.String("some message"), 92 Foo: aws.Int64(123), 93 }, 94 }, 95 "other error": { 96 Response: &http.Response{ 97 Header: http.Header{}, 98 Body: ioutil.NopCloser(strings.NewReader(otherErrJSON)), 99 }, 100 Expect: &OtherError{ 101 Message2: aws.String("some message"), 102 }, 103 }, 104 "other complex Code error": { 105 Response: &http.Response{ 106 Header: http.Header{}, 107 Body: ioutil.NopCloser(strings.NewReader(complexCodeErrJSON)), 108 }, 109 Expect: &OtherError{ 110 Message2: aws.String("some message"), 111 }, 112 }, 113 "complex error": { 114 Response: &http.Response{ 115 StatusCode: 400, 116 Header: http.Header{ 117 "Some-Header": []string{"headval"}, 118 }, 119 Body: ioutil.NopCloser(strings.NewReader(complexErrJSON)), 120 }, 121 Expect: &ComplexError{ 122 Message2: aws.String("some message"), 123 HeaderVal: aws.String("headval"), 124 Status: aws.Int64(400), 125 Foo: &ErrorNested{ 126 Bar: aws.String("abc123"), 127 Baz: aws.Int64(123), 128 }, 129 }, 130 }, 131 "unknown error": { 132 Response: &http.Response{ 133 Header: http.Header{}, 134 Body: ioutil.NopCloser(strings.NewReader(unknownErrJSON)), 135 }, 136 Expect: awserr.NewRequestFailure( 137 awserr.New("UnknownError", "error message", nil), 138 respMeta.StatusCode, 139 respMeta.RequestID, 140 ), 141 }, 142 "invalid error": { 143 Response: &http.Response{ 144 StatusCode: 400, 145 Header: http.Header{}, 146 Body: ioutil.NopCloser(strings.NewReader(`{`)), 147 }, 148 Err: "failed decoding", 149 }, 150 "unknown from header": { 151 Response: &http.Response{ 152 Header: http.Header{ 153 errorTypeHeader: []string{"UnknownError"}, 154 errorMessageHeader: []string{"error message"}, 155 }, 156 Body: ioutil.NopCloser(nil), 157 }, 158 Expect: awserr.NewRequestFailure( 159 awserr.New("UnknownError", "error message", nil), 160 respMeta.StatusCode, 161 respMeta.RequestID, 162 ), 163 }, 164 "code from header": { 165 Response: &http.Response{ 166 Header: http.Header{ 167 errorTypeHeader: []string{"SimpleError"}, 168 }, 169 Body: ioutil.NopCloser(strings.NewReader(simpleNoCodeJSON)), 170 }, 171 Expect: &SimpleError{ 172 Message2: aws.String("some message"), 173 Foo: aws.Int64(123), 174 }, 175 }, 176 "ignore message header": { 177 Response: &http.Response{ 178 Header: http.Header{ 179 errorTypeHeader: []string{"SimpleError"}, 180 errorMessageHeader: []string{"error message"}, 181 }, 182 Body: ioutil.NopCloser(strings.NewReader(simpleNoCodeJSON)), 183 }, 184 Expect: &SimpleError{ 185 Message2: aws.String("some message"), 186 Foo: aws.Int64(123), 187 }, 188 }, 189 } 190 191 for name, c := range cases { 192 t.Run(name, func(t *testing.T) { 193 u := NewUnmarshalTypedError(exceptions) 194 v, err := u.UnmarshalError(c.Response, respMeta) 195 196 if len(c.Err) != 0 { 197 if err == nil { 198 t.Fatalf("expect error, got none") 199 } 200 if e, a := c.Err, err.Error(); !strings.Contains(a, e) { 201 t.Fatalf("expect %v in error, got %v", e, a) 202 } 203 } else if err != nil { 204 t.Fatalf("expect no error, got %v", err) 205 } 206 207 if e, a := c.Expect, v; !reflect.DeepEqual(e, a) { 208 t.Errorf("expect %+#v, got %#+v", e, a) 209 } 210 }) 211 } 212} 213 214func TestUnmarshalError_SerializationError(t *testing.T) { 215 cases := map[string]struct { 216 Request *request.Request 217 ExpectMsg string 218 ExpectBytes []byte 219 }{ 220 "empty body": { 221 Request: &request.Request{ 222 Data: &struct{}{}, 223 HTTPResponse: &http.Response{ 224 StatusCode: 400, 225 Header: http.Header{ 226 "X-Amzn-Requestid": []string{"abc123"}, 227 }, 228 Body: ioutil.NopCloser( 229 bytes.NewReader([]byte{}), 230 ), 231 }, 232 }, 233 ExpectMsg: "error message missing", 234 }, 235 "HTML body": { 236 Request: &request.Request{ 237 Data: &struct{}{}, 238 HTTPResponse: &http.Response{ 239 StatusCode: 400, 240 Header: http.Header{ 241 "X-Amzn-Requestid": []string{"abc123"}, 242 }, 243 Body: ioutil.NopCloser( 244 bytes.NewReader([]byte(`<html></html>`)), 245 ), 246 }, 247 }, 248 ExpectBytes: []byte(`<html></html>`), 249 ExpectMsg: "failed decoding", 250 }, 251 } 252 253 for name, c := range cases { 254 t.Run(name, func(t *testing.T) { 255 req := c.Request 256 257 UnmarshalError(req) 258 if req.Error == nil { 259 t.Fatal("expect error, got none") 260 } 261 262 aerr := req.Error.(awserr.RequestFailure) 263 if e, a := request.ErrCodeSerialization, aerr.Code(); e != a { 264 t.Errorf("expect %v, got %v", e, a) 265 } 266 267 uerr := aerr.OrigErr().(awserr.UnmarshalError) 268 if e, a := c.ExpectMsg, uerr.Message(); !strings.Contains(a, e) { 269 t.Errorf("Expect %q, in %q", e, a) 270 } 271 if e, a := c.ExpectBytes, uerr.Bytes(); !bytes.Equal(e, a) { 272 t.Errorf("expect:\n%v\nactual:\n%v", hex.Dump(e), hex.Dump(a)) 273 } 274 }) 275 } 276} 277