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