forked from goadesign/goa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patherror.go
346 lines (311 loc) · 10.6 KB
/
error.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
// Package goa standardizes on structured error responses: a request that fails because of
// invalid input or unexpected condition produces a response that contains one or more structured
// error(s). Each error object has three keys: a id (number), a title and a message. The title
// for a given id is always the same, the intent is to provide a human friendly categorization.
// The message is specific to the error occurrence and provides additional details that often
// include contextual information (name of parameters etc.).
//
// The basic data structure backing errors is TypedError which simply contains the id and message.
// Multiple errors (not just TypedError instances) can be encapsulated in a MultiError. Both
// TypedError and MultiError implement the error interface, the Error methods return valid JSON
// that can be written directly to a response body.
//
// The code generated by goagen calls the helper functions exposed in this file when it encounters
// invalid data (wrong type, validation errors etc.) such as InvalidParamTypeError,
// InvalidAttributeTypeError etc. These methods take and return an error which is a MultiError that
// gets built over time. The final MultiError object then gets serialized into the response and sent
// back to the client. The response status code is inferred from the type wrapping the error object:
// a BadRequestError produces a 400 status code while any other error produce a 500. This behavior
// can be overridden by setting a custom ErrorHandler in the application.
package goa
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
)
type (
// ErrorID is an enum listing the possible types of errors.
ErrorID int
// TypedError describes an error that can be returned in a HTTP response.
TypedError struct {
ID ErrorID
Mesg string
}
// MultiError records multiple errors.
MultiError []error
// BadRequestError is the type of errors that result in a response with status code 400.
BadRequestError struct {
Actual error
}
)
const (
// ErrInvalidParamType is the error produced by the generated code when
// a request parameter type does not match the design.
ErrInvalidParamType = iota + 1
// ErrMissingParam is the error produced by the generated code when a
// required request parameter is missing.
ErrMissingParam
// ErrInvalidAttributeType is the error produced by the generated
// code when a data structure attribute type does not match the design
// definition.
ErrInvalidAttributeType
// ErrMissingAttribute is the error produced by the generated
// code when a data structure attribute required by the design
// definition is missing.
ErrMissingAttribute
// ErrInvalidEnumValue is the error produced by the generated code when
// a values does not match one of the values listed in the attribute
// definition as being valid (i.e. not part of the enum).
ErrInvalidEnumValue
// ErrMissingHeader is the error produced by the generated code when a
// required header is missing.
ErrMissingHeader
// ErrInvalidFormat is the error produced by the generated code when
// a value does not match the format specified in the attribute
// definition.
ErrInvalidFormat
// ErrInvalidPattern is the error produced by the generated code when
// a value does not match the regular expression specified in the
// attribute definition.
ErrInvalidPattern
// ErrInvalidRange is the error produced by the generated code when
// a value is less than the minimum specified in the design definition
// or more than the maximum.
ErrInvalidRange
// ErrInvalidLength is the error produced by the generated code when
// a value is a slice with less elements than the minimum length
// specified in the design definition or more elements than the
// maximum length.
ErrInvalidLength
// ErrInvalidVersion is the error rendered by the default mux when a
// request specifies an invalid version.
ErrInvalidVersion
)
// Title returns a human friendly error title
func (k ErrorID) Title() string {
switch k {
case ErrInvalidParamType:
return "invalid parameter value"
case ErrMissingParam:
return "missing required parameter"
case ErrInvalidAttributeType:
return "invalid attribute type"
case ErrMissingAttribute:
return "missing required attribute"
case ErrMissingHeader:
return "missing required HTTP header"
case ErrInvalidEnumValue:
return "invalid value"
case ErrInvalidFormat:
return "value does not match validation format"
case ErrInvalidPattern:
return "value does not match validation pattern"
case ErrInvalidRange:
return "invalid value range"
case ErrInvalidLength:
return "invalid value length"
case ErrInvalidVersion:
return "invalid version"
}
return "unknown error"
}
// MarshalJSON implements the json marshaler interface.
func (t *TypedError) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
ID int `json:"id" xml:"id"`
Title string `json:"title" xml:"title"`
Msg string `json:"msg" xml:"msg"`
}{
ID: int(t.ID),
Title: t.ID.Title(),
Msg: t.Mesg,
})
}
// Error builds an error message from the typed error details.
func (t *TypedError) Error() string {
IncrCounter([]string{"goa", "error", strconv.Itoa(int(t.ID))}, 1.0)
js, err := json.Marshal(t)
if err != nil {
return `{"id":0,"title":"generic","msg":"failed to serialize error"}`
}
return string(js)
}
// Error summarizes all the underlying error messages in one JSON array.
func (m MultiError) Error() string {
var buffer bytes.Buffer
buffer.WriteString("[")
for i, err := range m {
txt := err.Error()
if _, ok := err.(*TypedError); !ok {
b, err := json.Marshal(txt)
txt = `{"id":0,"title":"generic","msg":`
if err != nil {
txt += `"unknown error"}`
} else {
txt += string(b) + "}"
}
}
buffer.WriteString(txt)
if i < len(m)-1 {
buffer.WriteString(",")
}
}
buffer.WriteString("]")
// you can blame rsc for that: https://code.google.com/p/go/issues/detail?id=8592#c3
txt := buffer.String()
txt = strings.Replace(txt, "\\u0026", "&", -1)
txt = strings.Replace(txt, "\\u003c", "<", -1)
txt = strings.Replace(txt, "\\u003e", ">", -1)
return txt
}
// NewBadRequestError wraps the given error into a BadRequestError.
func NewBadRequestError(err error) *BadRequestError {
return &BadRequestError{Actual: err}
}
// Error implements error.
func (b *BadRequestError) Error() string {
return b.Actual.Error()
}
// InvalidParamTypeError appends a typed error of id ErrInvalidParamType to
// err and returns it.
func InvalidParamTypeError(name string, val interface{}, expected string, err error) error {
terr := TypedError{
ID: ErrInvalidParamType,
Mesg: fmt.Sprintf("invalid value %#v for parameter %#v, must be a %s",
val, name, expected),
}
return ReportError(err, &terr)
}
// MissingParamError appends a typed error of id ErrMissingParam to err and
// returns it.
func MissingParamError(name string, err error) error {
terr := TypedError{
ID: ErrMissingParam,
Mesg: fmt.Sprintf("missing required parameter %#v", name),
}
return ReportError(err, &terr)
}
// InvalidAttributeTypeError appends a typed error of id ErrIncompatibleType
// to err and returns it.
func InvalidAttributeTypeError(ctx string, val interface{}, expected string, err error) error {
terr := TypedError{
ID: ErrInvalidAttributeType,
Mesg: fmt.Sprintf("type of %s must be %s but got value %#v", ctx,
expected, val),
}
return ReportError(err, &terr)
}
// MissingAttributeError appends a typed error of id ErrMissingAttribute to
// err and returns it.
func MissingAttributeError(ctx, name string, err error) error {
terr := TypedError{
ID: ErrMissingAttribute,
Mesg: fmt.Sprintf("attribute %#v of %s is missing and required", name, ctx),
}
return ReportError(err, &terr)
}
// MissingHeaderError appends a typed error of id ErrMissingHeader to err and
// returns it.
func MissingHeaderError(name string, err error) error {
terr := TypedError{
ID: ErrMissingHeader,
Mesg: fmt.Sprintf("missing required HTTP header %#v", name),
}
return ReportError(err, &terr)
}
// InvalidEnumValueError appends a typed error of id ErrInvalidEnumValue to
// err and returns it.
func InvalidEnumValueError(ctx string, val interface{}, allowed []interface{}, err error) error {
elems := make([]string, len(allowed))
for i, a := range allowed {
elems[i] = fmt.Sprintf("%#v", a)
}
terr := TypedError{
ID: ErrInvalidEnumValue,
Mesg: fmt.Sprintf("value of %s must be one of %s but got value %#v", ctx,
strings.Join(elems, ", "), val),
}
return ReportError(err, &terr)
}
// InvalidFormatError appends a typed error of id ErrInvalidFormat to err and
// returns it.
func InvalidFormatError(ctx, target string, format Format, formatError, err error) error {
terr := TypedError{
ID: ErrInvalidFormat,
Mesg: fmt.Sprintf("%s must be formatted as a %s but got value %#v, %s",
ctx, format, target, formatError.Error()),
}
return ReportError(err, &terr)
}
// InvalidPatternError appends a typed error of id ErrInvalidPattern to err and
// returns it.
func InvalidPatternError(ctx, target string, pattern string, err error) error {
terr := TypedError{
ID: ErrInvalidPattern,
Mesg: fmt.Sprintf("%s must match the regexp %#v but got value %#v",
ctx, pattern, target),
}
return ReportError(err, &terr)
}
// InvalidRangeError appends a typed error of id ErrInvalidRange to err and
// returns it.
func InvalidRangeError(ctx string, target interface{}, value int, min bool, err error) error {
comp := "greater or equal"
if !min {
comp = "lesser or equal"
}
terr := TypedError{
ID: ErrInvalidRange,
Mesg: fmt.Sprintf("%s must be %s than %d but got value %#v",
ctx, comp, value, target),
}
return ReportError(err, &terr)
}
// InvalidLengthError appends a typed error of id ErrInvalidLength to err and
// returns it.
func InvalidLengthError(ctx string, target interface{}, ln, value int, min bool, err error) error {
comp := "greater or equal"
if !min {
comp = "lesser or equal"
}
terr := TypedError{
ID: ErrInvalidLength,
Mesg: fmt.Sprintf("length of %s must be %s than %d but got value %#v (len=%d)",
ctx, comp, value, target, ln),
}
return ReportError(err, &terr)
}
// ReportError coerces the first argument into a MultiError then appends the second argument and
// returns the resulting MultiError.
func ReportError(err error, err2 error) error {
if err == nil {
if err2 == nil {
return MultiError{}
}
if _, ok := err2.(MultiError); ok {
return err2
}
return MultiError{err2}
}
merr, ok := err.(MultiError)
if err2 == nil {
if ok {
return err
}
return MultiError{err}
}
merr2, ok2 := err2.(MultiError)
if ok {
if ok2 {
return append(merr, merr2...)
}
return append(merr, err2)
}
merr = MultiError{err}
if ok2 {
return append(merr, merr2...)
}
return append(merr, err2)
}