1package awserr
2
3import "fmt"
4
5// SprintError returns a string of the formatted error code.
6//
7// Both extra and origErr are optional.  If they are included their lines
8// will be added, but if they are not included their lines will be ignored.
9func SprintError(code, message, extra string, origErr error) string {
10	msg := fmt.Sprintf("%s: %s", code, message)
11	if extra != "" {
12		msg = fmt.Sprintf("%s\n\t%s", msg, extra)
13	}
14	if origErr != nil {
15		msg = fmt.Sprintf("%s\ncaused by: %s", msg, origErr.Error())
16	}
17	return msg
18}
19
20// A baseError wraps the code and message which defines an error. It also
21// can be used to wrap an original error object.
22//
23// Should be used as the root for errors satisfying the awserr.Error. Also
24// for any error which does not fit into a specific error wrapper type.
25type baseError struct {
26	// Classification of error
27	code string
28
29	// Detailed information about error
30	message string
31
32	// Optional original error this error is based off of. Allows building
33	// chained errors.
34	errs []error
35}
36
37// newBaseError returns an error object for the code, message, and errors.
38//
39// code is a short no whitespace phrase depicting the classification of
40// the error that is being created.
41//
42// message is the free flow string containing detailed information about the
43// error.
44//
45// origErrs is the error objects which will be nested under the new errors to
46// be returned.
47func newBaseError(code, message string, origErrs []error) *baseError {
48	b := &baseError{
49		code:    code,
50		message: message,
51		errs:    origErrs,
52	}
53
54	return b
55}
56
57// Error returns the string representation of the error.
58//
59// See ErrorWithExtra for formatting.
60//
61// Satisfies the error interface.
62func (b baseError) Error() string {
63	size := len(b.errs)
64	if size > 0 {
65		return SprintError(b.code, b.message, "", errorList(b.errs))
66	}
67
68	return SprintError(b.code, b.message, "", nil)
69}
70
71// String returns the string representation of the error.
72// Alias for Error to satisfy the stringer interface.
73func (b baseError) String() string {
74	return b.Error()
75}
76
77// Code returns the short phrase depicting the classification of the error.
78func (b baseError) Code() string {
79	return b.code
80}
81
82// Message returns the error details message.
83func (b baseError) Message() string {
84	return b.message
85}
86
87// OrigErr returns the original error if one was set. Nil is returned if no
88// error was set. This only returns the first element in the list. If the full
89// list is needed, use BatchedErrors.
90func (b baseError) OrigErr() error {
91	switch len(b.errs) {
92	case 0:
93		return nil
94	case 1:
95		return b.errs[0]
96	default:
97		if err, ok := b.errs[0].(Error); ok {
98			return NewBatchError(err.Code(), err.Message(), b.errs[1:])
99		}
100		return NewBatchError("BatchedErrors",
101			"multiple errors occurred", b.errs)
102	}
103}
104
105// OrigErrs returns the original errors if one was set. An empty slice is
106// returned if no error was set.
107func (b baseError) OrigErrs() []error {
108	return b.errs
109}
110
111// So that the Error interface type can be included as an anonymous field
112// in the requestError struct and not conflict with the error.Error() method.
113type awsError Error
114
115// A requestError wraps a request or service error.
116//
117// Composed of baseError for code, message, and original error.
118type requestError struct {
119	awsError
120	statusCode int
121	requestID  string
122}
123
124// newRequestError returns a wrapped error with additional information for
125// request status code, and service requestID.
126//
127// Should be used to wrap all request which involve service requests. Even if
128// the request failed without a service response, but had an HTTP status code
129// that may be meaningful.
130//
131// Also wraps original errors via the baseError.
132func newRequestError(err Error, statusCode int, requestID string) *requestError {
133	return &requestError{
134		awsError:   err,
135		statusCode: statusCode,
136		requestID:  requestID,
137	}
138}
139
140// Error returns the string representation of the error.
141// Satisfies the error interface.
142func (r requestError) Error() string {
143	extra := fmt.Sprintf("status code: %d, request id: %s",
144		r.statusCode, r.requestID)
145	return SprintError(r.Code(), r.Message(), extra, r.OrigErr())
146}
147
148// String returns the string representation of the error.
149// Alias for Error to satisfy the stringer interface.
150func (r requestError) String() string {
151	return r.Error()
152}
153
154// StatusCode returns the wrapped status code for the error
155func (r requestError) StatusCode() int {
156	return r.statusCode
157}
158
159// RequestID returns the wrapped requestID
160func (r requestError) RequestID() string {
161	return r.requestID
162}
163
164// OrigErrs returns the original errors if one was set. An empty slice is
165// returned if no error was set.
166func (r requestError) OrigErrs() []error {
167	if b, ok := r.awsError.(BatchedErrors); ok {
168		return b.OrigErrs()
169	}
170	return []error{r.OrigErr()}
171}
172
173// An error list that satisfies the golang interface
174type errorList []error
175
176// Error returns the string representation of the error.
177//
178// Satisfies the error interface.
179func (e errorList) Error() string {
180	msg := ""
181	// How do we want to handle the array size being zero
182	if size := len(e); size > 0 {
183		for i := 0; i < size; i++ {
184			msg += fmt.Sprintf("%s", e[i].Error())
185			// We check the next index to see if it is within the slice.
186			// If it is, then we append a newline. We do this, because unit tests
187			// could be broken with the additional '\n'
188			if i+1 < size {
189				msg += "\n"
190			}
191		}
192	}
193	return msg
194}
195