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