-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathhttp.go
222 lines (188 loc) · 4.88 KB
/
http.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
package errorutil
import (
"errors"
"net/http"
)
// HTTPStatusCodeEr defines errors that should return a specific HTTP status code
type HTTPStatusCodeEr interface {
HTTPStatusCode() int
}
// StatusCodeEr defines errors that should return a specific HTTP status code
type StatusCodeEr interface {
StatusCode() int
}
// HTTPStatusCode returns the status code that a HTTP handler should return.
//
// If the error is nil, StatusOK is returned.
//
// If the error implements HTTPStatusCodeEr or StatusCodeEr, it returns the corresponding status code.
//
// It tries to check some stdlib errors (testing the error string, to avoid importing unwanted packages),
// and returns appropriate status codes.
//
// Otherwise, StatusInternalServerError is returned.
func HTTPStatusCode(err error) int {
if err == nil {
return http.StatusOK
}
type causer interface {
Cause() error
}
for err != nil {
if status, ok := err.(HTTPStatusCodeEr); ok {
return status.HTTPStatusCode()
}
if status, ok := err.(StatusCodeEr); ok {
return status.StatusCode()
}
// Check errors from stdlib. Test string to avoid importing packages
switch err.Error() {
// package os
case "permission denied":
return http.StatusForbidden
case "file does not exist":
return http.StatusNotFound
case "storage: bucket doesn't exist":
return http.StatusNotFound
case "storage: object doesn't exist":
return http.StatusNotFound
// package database/sql
case "sql: no rows in result set":
return http.StatusNotFound
case "i/o timeout", "TLS handshake timeout":
return http.StatusRequestTimeout
}
cause, ok := err.(causer)
if ok {
err = cause.Cause()
} else {
err = errors.Unwrap(err)
}
}
return http.StatusInternalServerError
}
// HTTPError builds an error based on a http.Response. If status code is < 300 or 304, nil is returned.
// Otherwise, errors implementing the various interfaces (Retryabler, HTTPStatusCodeEr) are returned
func HTTPError(resp *http.Response) error {
if resp.StatusCode < 300 || resp.StatusCode == http.StatusNotModified {
return nil
}
return httpError(resp.StatusCode)
}
type httpError int
func (err httpError) Error() string {
switch err {
case 429:
return "Too Many Requests"
default:
return http.StatusText(int(err))
}
}
func (err httpError) HTTPStatusCode() int {
return int(err)
}
func (err httpError) StatusCode() int {
return int(err)
}
func (err httpError) Retryable() bool {
switch int(err) {
case http.StatusBadGateway, http.StatusGatewayTimeout, http.StatusServiceUnavailable, http.StatusInternalServerError:
return true
case 429:
return true
default:
return false
}
}
// NotFoundError marks an error as "not found". The calling http handler
// should return a StatusNotFound status code. It returns nil if the error is nil.
func NotFoundError(err error) error {
if err == nil {
return nil
}
return ¬FoundError{err: err}
}
type notFoundError struct {
err error
}
func (err *notFoundError) Error() string {
return err.err.Error()
}
func (err *notFoundError) HTTPStatusCode() int {
return http.StatusNotFound
}
func (err *notFoundError) IsRetryable() bool {
return false
}
func (err *notFoundError) Cause() error {
return err.err
}
// ForbiddenError marks an error as "access forbidden". The calling http handler
// should return a StatusForbidden status code. It returns nil if the error is nil.
func ForbiddenError(err error) error {
if err == nil {
return nil
}
return &forbiddenError{err: err}
}
type forbiddenError struct {
err error
}
func (err *forbiddenError) Error() string {
return err.err.Error()
}
func (err *forbiddenError) HTTPStatusCode() int {
return http.StatusForbidden
}
func (err *forbiddenError) IsRetryable() bool {
return false
}
func (err *forbiddenError) Cause() error {
return err.err
}
// InvalidError marks an error as "invalid". The calling http handler
// should return a StatusBadRequest status code. It returns nil if the error is nil.
func InvalidError(err error) error {
if err == nil {
return nil
}
return &invalidError{err: err}
}
type invalidError struct {
err error
}
func (err *invalidError) Error() string {
return err.err.Error()
}
func (err *invalidError) HTTPStatusCode() int {
return http.StatusBadRequest
}
func (err *invalidError) IsRetryable() bool {
return false
}
func (err *invalidError) Cause() error {
return err.err
}
// ConflictError marks an error as "conflict". The calling http handler
// should return a Conflict status code. It returns nil if the error is nil.
func ConflictError(err error) error {
if err == nil {
return nil
}
return &conflictError{err: err}
}
type conflictError struct {
err error
}
func (err *conflictError) Error() string {
return err.err.Error()
}
func (err *conflictError) HTTPStatusCode() int {
return http.StatusConflict
}
func (err *conflictError) IsRetryable() bool {
return false
}
func (err *conflictError) Cause() error {
return err.err
}