-
Notifications
You must be signed in to change notification settings - Fork 0
/
http_errors.go
298 lines (264 loc) · 6.03 KB
/
http_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
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
package hes
import (
"encoding/json"
"fmt"
"io"
"net/http"
"runtime"
"strings"
"sync"
)
const (
defaultStatusCode = http.StatusBadRequest
)
var callerEnabled = false
type (
// Error http error
Error struct {
lock *sync.RWMutex
// http status code
StatusCode int `json:"statusCode,omitempty"`
// error code
Code string `json:"code,omitempty"`
// category
Category string `json:"category,omitempty"`
// sub category
SubCategory string `json:"subCategory,omitempty"`
// title
Title string `json:"title,omitempty"`
// message
Message string `json:"message,omitempty"`
// exception error
Exception bool `json:"exception,omitempty"`
// original error
Err error `json:"-"`
// File caller file
File string `json:"file,omitempty"`
// Line caller line
Line int `json:"line,omitempty"`
// extra info for error
Extra map[string]interface{} `json:"extra,omitempty"`
// sub errors
Errs []*Error `json:"errs,omitempty"`
}
FileConvertor func(file string) string
)
// EnableCaller enable caller
func EnableCaller(enabled bool) {
callerEnabled = enabled
}
// fileConvertor file convertor
var fileConvertor FileConvertor
// SetFileConvertor set file convertor
func SetFileConvertor(fn FileConvertor) {
fileConvertor = fn
}
// Error error interface
func (e *Error) Error() string {
str := fmt.Sprintf("message=%s", e.Message)
if e.Code != "" {
str = fmt.Sprintf("code=%s, %s", e.Code, str)
}
if e.Category != "" {
str = fmt.Sprintf("category=%s, %s", e.Category, str)
}
if e.StatusCode != 0 {
str = fmt.Sprintf("statusCode=%d, %s", e.StatusCode, str)
}
if e.File != "" {
str = fmt.Sprintf("file=%s, line=%d, %s", e.File, e.Line, str)
}
if len(e.Errs) != 0 {
arr := make([]string, len(e.Errs))
for index, err := range e.Errs {
arr[index] = err.Error()
}
str = fmt.Sprintf("%s, errs:(%s)", str, strings.Join(arr, ","))
}
return str
}
// Format error format
func (e *Error) Format(s fmt.State, verb rune) {
switch verb {
default:
fallthrough
case 's':
_, _ = io.WriteString(s, e.Error())
case 'q':
fmt.Fprintf(s, "%q", e.Message)
}
}
// SetCaller set info of caller
func (e *Error) SetCaller(skip int) {
if e.lock != nil {
e.lock.Lock()
defer e.lock.Unlock()
}
_, file, line, _ := runtime.Caller(skip)
if fileConvertor != nil {
file = fileConvertor(file)
}
e.File = file
e.Line = line
}
// ToJSON error to json
func (e *Error) ToJSON() []byte {
buf, _ := json.Marshal(e)
return buf
}
// CloneWithMessage clone error and update message
func (e *Error) CloneWithMessage(message string) *Error {
clone := e.Clone()
clone.Message = message
return clone
}
// IsEmpty check the error list is empty
func (e *Error) IsEmpty() bool {
if e.lock != nil {
e.lock.RLock()
defer e.lock.RUnlock()
}
return len(e.Errs) == 0
}
// IsNotEmpty check the error list is not empty
func (e *Error) IsNotEmpty() bool {
// is empty有判断锁,因此不需要判断
return !e.IsEmpty()
}
// AddExtra add extra value to error
func (e *Error) AddExtra(key string, value interface{}) {
if e.lock != nil {
e.lock.Lock()
defer e.lock.Unlock()
}
if e.Extra == nil {
e.Extra = make(map[string]interface{})
}
e.Extra[key] = value
}
// Exists return true if it already exists
func (e *Error) exists(he *Error) bool {
for _, item := range e.Errs {
if he.Title == item.Title &&
he.Message == item.Message &&
he.Category == item.Category {
return true
}
}
return false
}
func (e *Error) add(errs ...error) {
if len(e.Errs) == 0 {
e.Errs = make([]*Error, 0)
}
for _, err := range errs {
if err == nil {
continue
}
he := Wrap(err)
// 如果包括子错误,则直接添加子错误列表
if he.IsNotEmpty() {
for _, err := range he.Errs {
e.add(err)
}
continue
}
// 判断是否已存在相同的error
// 如果已有相同error,则不添加
if e.exists(he) {
continue
}
e.Errs = append(e.Errs, he)
}
}
// Add add error to error list
func (e *Error) Add(errs ...error) {
if len(errs) == 0 {
return
}
if e.lock != nil {
e.lock.Lock()
defer e.lock.Unlock()
}
e.add(errs...)
}
// Clone clone error
func (e *Error) Clone() *Error {
he := new(Error)
*he = *e
if he.lock != nil {
he.lock = &sync.RWMutex{}
}
return he
}
func newError(message string, statusCode, skip int, category ...string) *Error {
he := &Error{
Message: message,
StatusCode: statusCode,
}
if len(category) != 0 {
he.Category = category[0]
}
if callerEnabled {
he.SetCaller(skip)
}
return he
}
// New create a http error
func New(message string, category ...string) *Error {
he := newError(message, defaultStatusCode, 3, category...)
return he
}
// NewMutex create a http error with mutex
func NewMutex(message string, category ...string) *Error {
he := New(message, category...)
he.lock = &sync.RWMutex{}
return he
}
// NewWithStatusCode create a http error with status code
func NewWithStatusCode(message string, statusCode int, category ...string) *Error {
he := newError(message, statusCode, 3, category...)
return he
}
// NewWithError create a http error with error
func NewWithError(err error) *Error {
he := newError(err.Error(), defaultStatusCode, 3)
he.Err = err
return he
}
// NewWithErrorStatusCode create a http error with error and status code
func NewWithErrorStatusCode(err error, statusCode int) *Error {
he := newError(err.Error(), statusCode, 3)
he.Err = err
return he
}
// NewWithCaller create a http error with caller
func NewWithCaller(message string) *Error {
he := &Error{
Message: message,
StatusCode: defaultStatusCode,
}
he.SetCaller(2)
return he
}
// NewWithException create a http error and set exception to true
func NewWithException(message string) *Error {
he := newError(message, defaultStatusCode, 3)
he.Exception = true
return he
}
// IsError check the error whether or not hes error
func IsError(err error) bool {
_, ok := err.(*Error)
return ok
}
// Wrap wrap error
func Wrap(err error) *Error {
he, ok := err.(*Error)
if ok {
return he.Clone()
}
he = newError(err.Error(), defaultStatusCode, 3)
he.Err = err
return he
}