forked from goadesign/goa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patherror.go
221 lines (197 loc) · 8.34 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
// Package goa standardizes on structured error responses: a request that fails because of
// invalid input or unexpected condition produces a response that contains a structured error.
// Error objects has four keys: a code, a status, a detail and metadata. The code defines the class
// of error (e.g. "invalid_parameter_type") and the status is the corresponding HTTP status
// (e.g. 400). The detail is specific to the error occurrence. The medata provides additional
// values that provide contextual information (name of parameters etc.).
//
// The basic data structure backing errors is Error. Instances of Error can be created via Error
// Class functions. See http://goa.design/implement/error_handling.html
//
// 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 return errors that get merged with any previously
// encountered error via the Error Merge method.
//
// goa includes an error handler middleware that takes care of mapping back any error returned by
// previously called middleware or action handler into HTTP responses. If the error is an instance
// of Error then the corresponding content including the HTTP status is used otherwise an internal
// error is returned. Errors that bubble up all the way to the top (i.e. not handled by the error
// middleware) also generate an internal error response.
package goa
import (
"fmt"
"strings"
)
var (
// ErrInvalidRequest is the class of errors produced by the generated code when
// a request parameter or payload fails to validate.
ErrInvalidRequest = NewErrorClass("invalid_request", 400)
// ErrInvalidEncoding is the error produced when a request body fails
// to be decoded.
ErrInvalidEncoding = NewErrorClass("invalid_encoding", 400)
// ErrNoSecurityScheme is the error produced when no security scheme has been
// registered for a name defined in the design.
ErrNoSecurityScheme = NewErrorClass("no_security_scheme", 500)
// ErrBadRequest is a generic bad request error.
ErrBadRequest = NewErrorClass("bad_request", 400)
// ErrInternal is the class of error used for non Error.
ErrInternal = NewErrorClass("internal", 500)
)
type (
// Error contains the details of a error response.
Error struct {
// Code identifies the class of errors for client programs.
Code string `json:"code" xml:"code"`
// Status is the HTTP status code used by responses that cary the error.
Status int `json:"status" xml:"status"`
// Detail describes the specific error occurrence.
Detail string `json:"detail" xml:"detail"`
// MetaValues contains additional key/value pairs useful to clients.
MetaValues map[string]interface{} `json:"meta,omitempty" xml:"meta,omitempty"`
}
// ErrorClass is an error generating function.
// It accepts a format and values and produces errors with the resulting string.
// If the format is a string or a Stringer then the string value is used.
// If the format is an error then the string returned by Error() is used.
// Otherwise the string produced using fmt.Sprintf("%v") is used.
ErrorClass func(fm interface{}, v ...interface{}) *Error
)
// NewErrorClass creates a new error class.
// It is the responsability of the client to guarantee uniqueness of code.
func NewErrorClass(code string, status int) ErrorClass {
return func(fm interface{}, v ...interface{}) *Error {
var f string
switch actual := fm.(type) {
case string:
f = actual
case error:
f = actual.Error()
case fmt.Stringer:
f = actual.String()
default:
f = fmt.Sprintf("%v", actual)
}
return &Error{Code: code, Status: status, Detail: fmt.Sprintf(f, v...)}
}
}
// InvalidParamTypeError creates a Error with class ID ErrInvalidParamType
func InvalidParamTypeError(name string, val interface{}, expected string) *Error {
return ErrInvalidRequest("invalid value %#v for parameter %#v, must be a %s", val, name, expected)
}
// MissingParamError creates a Error with class ID ErrMissingParam
func MissingParamError(name string) *Error {
return ErrInvalidRequest("missing required parameter %#v", name)
}
// InvalidAttributeTypeError creates a Error with class ID ErrInvalidAttributeType
func InvalidAttributeTypeError(ctx string, val interface{}, expected string) *Error {
return ErrInvalidRequest("type of %s must be %s but got value %#v", ctx, expected, val)
}
// MissingAttributeError creates a Error with class ID ErrMissingAttribute
func MissingAttributeError(ctx, name string) *Error {
return ErrInvalidRequest("attribute %#v of %s is missing and required", name, ctx)
}
// MissingHeaderError creates a Error with class ID ErrMissingHeader
func MissingHeaderError(name string) *Error {
return ErrInvalidRequest("missing required HTTP header %#v", name)
}
// InvalidEnumValueError creates a Error with class ID ErrInvalidEnumValue
func InvalidEnumValueError(ctx string, val interface{}, allowed []interface{}) *Error {
elems := make([]string, len(allowed))
for i, a := range allowed {
elems[i] = fmt.Sprintf("%#v", a)
}
return ErrInvalidRequest("value of %s must be one of %s but got value %#v", ctx, strings.Join(elems, ", "), val)
}
// InvalidFormatError creates a Error with class ID ErrInvalidFormat
func InvalidFormatError(ctx, target string, format Format, formatError error) *Error {
return ErrInvalidRequest("%s must be formatted as a %s but got value %#v, %s", ctx, format, target, formatError.Error())
}
// InvalidPatternError creates a Error with class ID ErrInvalidPattern
func InvalidPatternError(ctx, target string, pattern string) *Error {
return ErrInvalidRequest("%s must match the regexp %#v but got value %#v", ctx, pattern, target)
}
// InvalidRangeError creates a Error with class ID ErrInvalidRange
func InvalidRangeError(ctx string, target interface{}, value int, min bool) *Error {
comp := "greater or equal"
if !min {
comp = "lesser or equal"
}
return ErrInvalidRequest("%s must be %s than %d but got value %#v", ctx, comp, value, target)
}
// InvalidLengthError creates a Error with class ID ErrInvalidLength
func InvalidLengthError(ctx string, target interface{}, ln, value int, min bool) *Error {
comp := "greater or equal"
if !min {
comp = "lesser or equal"
}
return ErrInvalidRequest("length of %s must be %s than %d but got value %#v (len=%d)", ctx, comp, value, target, ln)
}
// NoSecurityScheme creates a Error with class ID ErrNoSecurityScheme
func NoSecurityScheme(schemeName string) *Error {
return ErrNoSecurityScheme("invalid security scheme %s", schemeName)
}
// Error returns the error occurrence details.
func (e *Error) Error() string {
return fmt.Sprintf("%d %s: %s", e.Status, e.Code, e.Detail)
}
// Meta adds to the error metadata.
func (e *Error) Meta(keyvals ...interface{}) *Error {
for i := 0; i < len(keyvals); i += 2 {
k := keyvals[i]
var v interface{} = "MISSING"
if i+1 < len(keyvals) {
v = keyvals[i+1]
}
e.MetaValues[fmt.Sprintf("%v", k)] = v
}
return e
}
// MergeErrors updates an error by merging another into it. It first converts other into an Error
// if not already one - producing an internal error in that case. The merge algorithm is then:
//
// * If any of e or other is an internal error then the result is an internal error
//
// * If the status or code of e and other don't match then the result is a 400 "bad_request"
//
// The Detail field is updated by concatenating the Detail fields of e and other separated
// by a newline. The MetaValues field of is updated by merging the map of other MetaValues
// into e's where values in e with identical keys to values in other get overwritten.
//
// Merge returns the updated error. This is useful in case the error was initially nil in
// which case other is returned.
func MergeErrors(err, other error) error {
if err == nil {
if other == nil {
return nil
}
return asError(other)
}
if other == nil {
return asError(err)
}
e := asError(err)
o := asError(other)
switch {
case e.Status == 500 || o.Status == 500:
if e.Status != 500 {
e.Status = 500
e.Code = "internal_error"
}
case e.Status != o.Status || e.Code != o.Code:
e.Status = 400
e.Code = "bad_request"
}
e.Detail = e.Detail + "\n" + o.Detail
for n, v := range o.MetaValues {
e.MetaValues[n] = v
}
return e
}
func asError(err error) *Error {
e, ok := err.(*Error)
if !ok {
return &Error{Status: 500, Code: "internal_error", Detail: err.Error()}
}
return e
}