-
Notifications
You must be signed in to change notification settings - Fork 14
/
errors.go
192 lines (160 loc) · 5.16 KB
/
errors.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
package common
import (
"context"
"fmt"
"net/http"
)
type Kind int
const (
UnknownError Kind = iota
BadRequestError
InternalError
UnauthorizedError
DownstreamUnavailableError
DownstreamTimeoutError
DownstreamUnauthorizedError // 401 from downstream
DownstreamUnexpectedResponseError // unexpected response from downstream
DownstreamResponseError // application-leve error response from downstream
)
const downstreamResponseSnippetMaxLength = 128
func (k Kind) String() string {
switch k {
case BadRequestError:
return "Missing one or more of the required parameters"
case InternalError:
return "Internal Server Error"
case UnauthorizedError:
return "Unauthorized error"
case DownstreamUnavailableError:
return "Downstream system is unavailable"
case DownstreamTimeoutError:
return "Time out from down stream services"
case DownstreamUnauthorizedError:
return "Unauthorized error from downstream services"
case DownstreamUnexpectedResponseError:
return "Unexpected response from downstream services"
case DownstreamResponseError:
return "Error response from downstream services"
default:
return "Internal Server Error"
}
}
type ErrorKinder interface {
ErrorKind() Kind
}
// If the error returned is an ErrorWriter the error handling will call the writeError method before any of the
// regular error handling (no mapping).
//
// If the call returns true it means it wrote the error and will not do any more handling.
// If it returns false it will go through the normal error writing path (via both the MapError and WriteError callbacks).
type ErrorWriter interface {
WriteError(ctx context.Context, w http.ResponseWriter) bool
}
type ServerError struct {
Kind Kind
Message string
Cause error
}
func (e *ServerError) Error() string {
return fmt.Sprintf("ServerError(Kind=%s, Message=%s, Cause=%s)", e.Kind, e.Message, e.Cause)
}
func (e *ServerError) ErrorKind() Kind {
return e.Kind
}
func (e *ServerError) Unwrap() error {
return e.Cause
}
func CreateError(ctx context.Context, kind Kind, message string, cause error) error {
// we may push the error to NR here
if err := CheckContextTimeout(ctx, message, cause); err != nil {
return err
}
switch cause.(type) {
case ErrorKinder, CustomError, wrappedError:
return cause
default:
return &ServerError{Kind: kind, Message: message, Cause: cause}
}
}
func CheckContextTimeout(ctx context.Context, message string, cause error) error {
if ctx.Err() == context.DeadlineExceeded {
return &ServerError{Kind: DownstreamTimeoutError, Message: message, Cause: cause}
}
return nil
}
type DownstreamError struct {
Kind Kind
Response *http.Response
Body []byte
Cause error
}
func (e *DownstreamError) ErrorKind() Kind {
return e.Kind
}
func (e *DownstreamError) Error() string {
return fmt.Sprintf("DownstreamError(Kind=%s, Method=%s, URL=%s, StatusCode=%d, ContentType=%s, ContentLength=%d, Snippet=%s, Cause=%s)",
e.Kind.String(),
e.Response.Request.Method,
e.Response.Request.URL.String(),
e.Response.StatusCode,
e.Response.Header.Get("Content-Type"),
e.Response.ContentLength,
string(e.Body),
e.Cause)
}
func (e *DownstreamError) Unwrap() error {
return e.Cause
}
func CreateDownstreamError(ctx context.Context, kind Kind, response *http.Response, body []byte, cause error) error {
// we may push the error to NR here
// add the request method and url as message, make the troubleshooting easier
if err := CheckContextTimeout(ctx, fmt.Sprintf("%s %s", response.Request.Method, response.Request.URL.String()), cause); err != nil {
return err
}
err := &DownstreamError{
Kind: kind,
Response: response,
Cause: cause,
}
bodyLength := len(body)
switch {
case bodyLength == 0:
case bodyLength > downstreamResponseSnippetMaxLength:
err.Body = body[:downstreamResponseSnippetMaxLength]
default:
err.Body = body
}
return err
}
type ZeroHeaderLengthError struct {
paramCanonical string
}
func NewZeroHeaderLengthError(param string) error {
return &ZeroHeaderLengthError{paramCanonical: http.CanonicalHeaderKey(param)}
}
func (e *ZeroHeaderLengthError) Error() string {
return fmt.Sprintf("%s header length is zero", e.paramCanonical)
}
func (e *ZeroHeaderLengthError) CausedByParam(param string) bool {
return e.paramCanonical == http.CanonicalHeaderKey(param)
}
type InvalidHeaderError struct {
paramCanonical string
cause error
}
func NewInvalidHeaderError(param string, cause error) error {
return &InvalidHeaderError{paramCanonical: http.CanonicalHeaderKey(param), cause: cause}
}
func (e *InvalidHeaderError) Error() string {
return fmt.Sprintf("%s header is invalid according to the spec", e.paramCanonical)
}
func (e *InvalidHeaderError) CausedByParam(param string) bool {
return e.paramCanonical == http.CanonicalHeaderKey(param)
}
// This may be nil (for example if the cause was a regular expression mismatch) or may be InvalidValidationError for
// bad values passed in and nil or ValidationErrors as error otherwise.
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors
// (there may be more than one).
func (e *InvalidHeaderError) GetCause() error {
return e.cause
}