1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package docker
18
19import (
20	"encoding/json"
21	"fmt"
22	"strings"
23)
24
25// ErrorCoder is the base interface for ErrorCode and Error allowing
26// users of each to just call ErrorCode to get the real ID of each
27type ErrorCoder interface {
28	ErrorCode() ErrorCode
29}
30
31// ErrorCode represents the error type. The errors are serialized via strings
32// and the integer format may change and should *never* be exported.
33type ErrorCode int
34
35var _ error = ErrorCode(0)
36
37// ErrorCode just returns itself
38func (ec ErrorCode) ErrorCode() ErrorCode {
39	return ec
40}
41
42// Error returns the ID/Value
43func (ec ErrorCode) Error() string {
44	// NOTE(stevvooe): Cannot use message here since it may have unpopulated args.
45	return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1))
46}
47
48// Descriptor returns the descriptor for the error code.
49func (ec ErrorCode) Descriptor() ErrorDescriptor {
50	d, ok := errorCodeToDescriptors[ec]
51
52	if !ok {
53		return ErrorCodeUnknown.Descriptor()
54	}
55
56	return d
57}
58
59// String returns the canonical identifier for this error code.
60func (ec ErrorCode) String() string {
61	return ec.Descriptor().Value
62}
63
64// Message returned the human-readable error message for this error code.
65func (ec ErrorCode) Message() string {
66	return ec.Descriptor().Message
67}
68
69// MarshalText encodes the receiver into UTF-8-encoded text and returns the
70// result.
71func (ec ErrorCode) MarshalText() (text []byte, err error) {
72	return []byte(ec.String()), nil
73}
74
75// UnmarshalText decodes the form generated by MarshalText.
76func (ec *ErrorCode) UnmarshalText(text []byte) error {
77	desc, ok := idToDescriptors[string(text)]
78
79	if !ok {
80		desc = ErrorCodeUnknown.Descriptor()
81	}
82
83	*ec = desc.Code
84
85	return nil
86}
87
88// WithMessage creates a new Error struct based on the passed-in info and
89// overrides the Message property.
90func (ec ErrorCode) WithMessage(message string) Error {
91	return Error{
92		Code:    ec,
93		Message: message,
94	}
95}
96
97// WithDetail creates a new Error struct based on the passed-in info and
98// set the Detail property appropriately
99func (ec ErrorCode) WithDetail(detail interface{}) Error {
100	return Error{
101		Code:    ec,
102		Message: ec.Message(),
103	}.WithDetail(detail)
104}
105
106// WithArgs creates a new Error struct and sets the Args slice
107func (ec ErrorCode) WithArgs(args ...interface{}) Error {
108	return Error{
109		Code:    ec,
110		Message: ec.Message(),
111	}.WithArgs(args...)
112}
113
114// Error provides a wrapper around ErrorCode with extra Details provided.
115type Error struct {
116	Code    ErrorCode   `json:"code"`
117	Message string      `json:"message"`
118	Detail  interface{} `json:"detail,omitempty"`
119
120	// TODO(duglin): See if we need an "args" property so we can do the
121	// variable substitution right before showing the message to the user
122}
123
124var _ error = Error{}
125
126// ErrorCode returns the ID/Value of this Error
127func (e Error) ErrorCode() ErrorCode {
128	return e.Code
129}
130
131// Error returns a human readable representation of the error.
132func (e Error) Error() string {
133	return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message)
134}
135
136// WithDetail will return a new Error, based on the current one, but with
137// some Detail info added
138func (e Error) WithDetail(detail interface{}) Error {
139	return Error{
140		Code:    e.Code,
141		Message: e.Message,
142		Detail:  detail,
143	}
144}
145
146// WithArgs uses the passed-in list of interface{} as the substitution
147// variables in the Error's Message string, but returns a new Error
148func (e Error) WithArgs(args ...interface{}) Error {
149	return Error{
150		Code:    e.Code,
151		Message: fmt.Sprintf(e.Code.Message(), args...),
152		Detail:  e.Detail,
153	}
154}
155
156// ErrorDescriptor provides relevant information about a given error code.
157type ErrorDescriptor struct {
158	// Code is the error code that this descriptor describes.
159	Code ErrorCode
160
161	// Value provides a unique, string key, often captilized with
162	// underscores, to identify the error code. This value is used as the
163	// keyed value when serializing api errors.
164	Value string
165
166	// Message is a short, human readable decription of the error condition
167	// included in API responses.
168	Message string
169
170	// Description provides a complete account of the errors purpose, suitable
171	// for use in documentation.
172	Description string
173
174	// HTTPStatusCode provides the http status code that is associated with
175	// this error condition.
176	HTTPStatusCode int
177}
178
179// ParseErrorCode returns the value by the string error code.
180// `ErrorCodeUnknown` will be returned if the error is not known.
181func ParseErrorCode(value string) ErrorCode {
182	ed, ok := idToDescriptors[value]
183	if ok {
184		return ed.Code
185	}
186
187	return ErrorCodeUnknown
188}
189
190// Errors provides the envelope for multiple errors and a few sugar methods
191// for use within the application.
192type Errors []error
193
194var _ error = Errors{}
195
196func (errs Errors) Error() string {
197	switch len(errs) {
198	case 0:
199		return "<nil>"
200	case 1:
201		return errs[0].Error()
202	default:
203		msg := "errors:\n"
204		for _, err := range errs {
205			msg += err.Error() + "\n"
206		}
207		return msg
208	}
209}
210
211// Len returns the current number of errors.
212func (errs Errors) Len() int {
213	return len(errs)
214}
215
216// MarshalJSON converts slice of error, ErrorCode or Error into a
217// slice of Error - then serializes
218func (errs Errors) MarshalJSON() ([]byte, error) {
219	var tmpErrs struct {
220		Errors []Error `json:"errors,omitempty"`
221	}
222
223	for _, daErr := range errs {
224		var err Error
225
226		switch daErr := daErr.(type) {
227		case ErrorCode:
228			err = daErr.WithDetail(nil)
229		case Error:
230			err = daErr
231		default:
232			err = ErrorCodeUnknown.WithDetail(daErr)
233
234		}
235
236		// If the Error struct was setup and they forgot to set the
237		// Message field (meaning its "") then grab it from the ErrCode
238		msg := err.Message
239		if msg == "" {
240			msg = err.Code.Message()
241		}
242
243		tmpErrs.Errors = append(tmpErrs.Errors, Error{
244			Code:    err.Code,
245			Message: msg,
246			Detail:  err.Detail,
247		})
248	}
249
250	return json.Marshal(tmpErrs)
251}
252
253// UnmarshalJSON deserializes []Error and then converts it into slice of
254// Error or ErrorCode
255func (errs *Errors) UnmarshalJSON(data []byte) error {
256	var tmpErrs struct {
257		Errors []Error
258	}
259
260	if err := json.Unmarshal(data, &tmpErrs); err != nil {
261		return err
262	}
263
264	var newErrs Errors
265	for _, daErr := range tmpErrs.Errors {
266		// If Message is empty or exactly matches the Code's message string
267		// then just use the Code, no need for a full Error struct
268		if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) {
269			// Error's w/o details get converted to ErrorCode
270			newErrs = append(newErrs, daErr.Code)
271		} else {
272			// Error's w/ details are untouched
273			newErrs = append(newErrs, Error{
274				Code:    daErr.Code,
275				Message: daErr.Message,
276				Detail:  daErr.Detail,
277			})
278		}
279	}
280
281	*errs = newErrs
282	return nil
283}
284