1package jsonrpc
2
3import (
4	"bytes"
5	"io"
6	"io/ioutil"
7	"net/http"
8	"strings"
9
10	"github.com/aws/aws-sdk-go/aws/awserr"
11	"github.com/aws/aws-sdk-go/aws/request"
12	"github.com/aws/aws-sdk-go/private/protocol"
13	"github.com/aws/aws-sdk-go/private/protocol/json/jsonutil"
14)
15
16// UnmarshalTypedError provides unmarshaling errors API response errors
17// for both typed and untyped errors.
18type UnmarshalTypedError struct {
19	exceptions map[string]func(protocol.ResponseMetadata) error
20}
21
22// NewUnmarshalTypedError returns an UnmarshalTypedError initialized for the
23// set of exception names to the error unmarshalers
24func NewUnmarshalTypedError(exceptions map[string]func(protocol.ResponseMetadata) error) *UnmarshalTypedError {
25	return &UnmarshalTypedError{
26		exceptions: exceptions,
27	}
28}
29
30// UnmarshalError attempts to unmarshal the HTTP response error as a known
31// error type. If unable to unmarshal the error type, the generic SDK error
32// type will be used.
33func (u *UnmarshalTypedError) UnmarshalError(
34	resp *http.Response,
35	respMeta protocol.ResponseMetadata,
36) (error, error) {
37
38	var buf bytes.Buffer
39	var jsonErr jsonErrorResponse
40	teeReader := io.TeeReader(resp.Body, &buf)
41	err := jsonutil.UnmarshalJSONError(&jsonErr, teeReader)
42	if err != nil {
43		return nil, err
44	}
45	body := ioutil.NopCloser(&buf)
46
47	// Code may be separated by hash(#), with the last element being the code
48	// used by the SDK.
49	codeParts := strings.SplitN(jsonErr.Code, "#", 2)
50	code := codeParts[len(codeParts)-1]
51	msg := jsonErr.Message
52
53	if fn, ok := u.exceptions[code]; ok {
54		// If exception code is know, use associated constructor to get a value
55		// for the exception that the JSON body can be unmarshaled into.
56		v := fn(respMeta)
57		err := jsonutil.UnmarshalJSONCaseInsensitive(v, body)
58		if err != nil {
59			return nil, err
60		}
61
62		return v, nil
63	}
64
65	// fallback to unmodeled generic exceptions
66	return awserr.NewRequestFailure(
67		awserr.New(code, msg, nil),
68		respMeta.StatusCode,
69		respMeta.RequestID,
70	), nil
71}
72
73// UnmarshalErrorHandler is a named request handler for unmarshaling jsonrpc
74// protocol request errors
75var UnmarshalErrorHandler = request.NamedHandler{
76	Name: "awssdk.jsonrpc.UnmarshalError",
77	Fn:   UnmarshalError,
78}
79
80// UnmarshalError unmarshals an error response for a JSON RPC service.
81func UnmarshalError(req *request.Request) {
82	defer req.HTTPResponse.Body.Close()
83
84	var jsonErr jsonErrorResponse
85	err := jsonutil.UnmarshalJSONError(&jsonErr, req.HTTPResponse.Body)
86	if err != nil {
87		req.Error = awserr.NewRequestFailure(
88			awserr.New(request.ErrCodeSerialization,
89				"failed to unmarshal error message", err),
90			req.HTTPResponse.StatusCode,
91			req.RequestID,
92		)
93		return
94	}
95
96	codes := strings.SplitN(jsonErr.Code, "#", 2)
97	req.Error = awserr.NewRequestFailure(
98		awserr.New(codes[len(codes)-1], jsonErr.Message, nil),
99		req.HTTPResponse.StatusCode,
100		req.RequestID,
101	)
102}
103
104type jsonErrorResponse struct {
105	Code    string `json:"__type"`
106	Message string `json:"message"`
107}
108