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