1//go:build go1.7
2// +build go1.7
3
4package jsonrpc
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 = `{"__type":"UnknownError", "message":"error message", "something":123}`
22const simpleErrJSON = `{"__type":"SimpleError", "message":"some message", "foo":123}`
23const simpleCasedErrJSON = `{"__type":"SimpleError", "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 = `{"__type":"OtherError", "message":"some message"}`
34const complexCodeErrJSON = `{"__type":"foo.bar#OtherError", "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 = `{"__type":"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}
52type ErrorNested struct {
53	_ struct{} `type:"structure"`
54
55	Bar *string `type:"string" locationName:"bar"`
56	Baz *int64  `type:"integer" locationName:"baz"`
57}
58
59func TestUnmarshalTypedError(t *testing.T) {
60
61	respMeta := protocol.ResponseMetadata{
62		StatusCode: 400,
63		RequestID:  "abc123",
64	}
65
66	exceptions := map[string]func(protocol.ResponseMetadata) error{
67		"SimpleError": func(meta protocol.ResponseMetadata) error {
68			return &SimpleError{}
69		},
70		"OtherError": func(meta protocol.ResponseMetadata) error {
71			return &OtherError{}
72		},
73		"ComplexError": func(meta protocol.ResponseMetadata) error {
74			return &ComplexError{}
75		},
76	}
77
78	cases := map[string]struct {
79		Response *http.Response
80		Expect   error
81		Err      string
82	}{
83		"simple error": {
84			Response: &http.Response{
85				Header: http.Header{},
86				Body:   ioutil.NopCloser(strings.NewReader(simpleErrJSON)),
87			},
88			Expect: &SimpleError{
89				Message2: aws.String("some message"),
90				Foo:      aws.Int64(123),
91			},
92		},
93		"other error": {
94			Response: &http.Response{
95				Header: http.Header{},
96				Body:   ioutil.NopCloser(strings.NewReader(otherErrJSON)),
97			},
98			Expect: &OtherError{
99				Message2: aws.String("some message"),
100			},
101		},
102		"other complex Code error": {
103			Response: &http.Response{
104				Header: http.Header{},
105				Body:   ioutil.NopCloser(strings.NewReader(complexCodeErrJSON)),
106			},
107			Expect: &OtherError{
108				Message2: aws.String("some message"),
109			},
110		},
111		"complex error": {
112			Response: &http.Response{
113				Header: http.Header{},
114				Body:   ioutil.NopCloser(strings.NewReader(complexErrJSON)),
115			},
116			Expect: &ComplexError{
117				Message2: aws.String("some message"),
118				Foo: &ErrorNested{
119					Bar: aws.String("abc123"),
120					Baz: aws.Int64(123),
121				},
122			},
123		},
124		"unknown error": {
125			Response: &http.Response{
126				Header: http.Header{},
127				Body:   ioutil.NopCloser(strings.NewReader(unknownErrJSON)),
128			},
129			Expect: awserr.NewRequestFailure(
130				awserr.New("UnknownError", "error message", nil),
131				respMeta.StatusCode,
132				respMeta.RequestID,
133			),
134		},
135		"invalid error": {
136			Response: &http.Response{
137				StatusCode: 400,
138				Header:     http.Header{},
139				Body:       ioutil.NopCloser(strings.NewReader(`{`)),
140			},
141			Err: "failed decoding",
142		},
143		"mixed case fields": {
144			Response: &http.Response{
145				Header: http.Header{},
146				Body:   ioutil.NopCloser(strings.NewReader(simpleCasedErrJSON)),
147			},
148			Expect: &SimpleError{
149				Message2: aws.String("some message"),
150				Foo:      aws.Int64(123),
151			},
152		},
153	}
154
155	for name, c := range cases {
156		t.Run(name, func(t *testing.T) {
157			u := NewUnmarshalTypedError(exceptions)
158			v, err := u.UnmarshalError(c.Response, respMeta)
159
160			if len(c.Err) != 0 {
161				if err == nil {
162					t.Fatalf("expect error, got none")
163				}
164				if e, a := c.Err, err.Error(); !strings.Contains(a, e) {
165					t.Fatalf("expect %v in error, got %v", e, a)
166				}
167			} else if err != nil {
168				t.Fatalf("expect no error, got %v", err)
169			}
170
171			if e, a := c.Expect, v; !reflect.DeepEqual(e, a) {
172				t.Errorf("expect %+#v, got %#+v", e, a)
173			}
174		})
175	}
176}
177
178func TestUnmarshalError_SerializationError(t *testing.T) {
179	cases := map[string]struct {
180		Request     *request.Request
181		ExpectMsg   string
182		ExpectBytes []byte
183	}{
184		"empty body": {
185			Request: &request.Request{
186				Data: &struct{}{},
187				HTTPResponse: &http.Response{
188					StatusCode: 400,
189					Header: http.Header{
190						"X-Amzn-Requestid": []string{"abc123"},
191					},
192					Body: ioutil.NopCloser(
193						bytes.NewReader([]byte{}),
194					),
195				},
196			},
197			ExpectMsg: "error message missing",
198		},
199		"HTML body": {
200			Request: &request.Request{
201				Data: &struct{}{},
202				HTTPResponse: &http.Response{
203					StatusCode: 400,
204					Header: http.Header{
205						"X-Amzn-Requestid": []string{"abc123"},
206					},
207					Body: ioutil.NopCloser(
208						bytes.NewReader([]byte(`<html></html>`)),
209					),
210				},
211			},
212			ExpectBytes: []byte(`<html></html>`),
213			ExpectMsg:   "failed decoding",
214		},
215	}
216
217	for name, c := range cases {
218		t.Run(name, func(t *testing.T) {
219			req := c.Request
220
221			UnmarshalError(req)
222			if req.Error == nil {
223				t.Fatal("expect error, got none")
224			}
225
226			aerr := req.Error.(awserr.RequestFailure)
227			if e, a := request.ErrCodeSerialization, aerr.Code(); e != a {
228				t.Errorf("expect %v, got %v", e, a)
229			}
230
231			uerr := aerr.OrigErr().(awserr.UnmarshalError)
232			if e, a := c.ExpectMsg, uerr.Message(); !strings.Contains(a, e) {
233				t.Errorf("Expect %q, in %q", e, a)
234			}
235			if e, a := c.ExpectBytes, uerr.Bytes(); !bytes.Equal(e, a) {
236				t.Errorf("expect:\n%v\nactual:\n%v", hex.Dump(e), hex.Dump(a))
237			}
238		})
239	}
240}
241