-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxerror.go
More file actions
149 lines (135 loc) · 3.3 KB
/
Copy pathxerror.go
File metadata and controls
149 lines (135 loc) · 3.3 KB
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
package xerror
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"github.com/gomooth/xerror/ecode"
"github.com/gomooth/xerror/xcode"
)
// XError 结构化错误接口。
// 消费者可按需使用更小的接口:
// - CodeCarrier: 只需错误码和 HTTP 状态码时
// - FieldCarrier: 只需结构化字段时
// - Messenger: 只需消息时
type XError interface {
error
CodeCarrier
FieldCarrier
Messenger
// Unwrap 解包获得 error
Unwrap() error
// ToMessage 将错误转成消息
ToMessage(config *ecode.Config) string
// Is 判断是否与目标错误相等
Is(target error) bool
// WithHttpStatus 修改 HTTP 状态码,返回新的 XError。
WithHttpStatus(httpStatus int) XError
}
type xError struct {
errorBase
error error
}
func (e *xError) Message() string { return e.code.String() }
func newErrStr(code xcode.XCode, chainMsg string) string {
if code == nil || code.Code() == xcode.CodeNone {
return chainMsg
}
return appendCodePrefix(code.Code(), chainMsg)
}
// appendCodePrefix 将 [code] 前缀拼接到消息前。独立函数以利于编译器内联。
func appendCodePrefix(codeVal int, chainMsg string) string {
var buf []byte
buf = append(buf, '[')
buf = strconv.AppendInt(buf, int64(codeVal), 10)
buf = append(buf, ']', ' ')
buf = append(buf, chainMsg...)
return string(buf)
}
func (e *xError) Unwrap() error {
return e.error
}
func (e *xError) Is(target error) bool {
switch t := target.(type) {
case *xError:
if isCodeMatch(e.ErrorCode(), t.ErrorCode()) {
return true
}
return e == t
case *xJoinedError:
if isCodeMatch(e.ErrorCode(), t.ErrorCode()) {
return true
}
default:
var xe XError
if !errors.As(target, &xe) {
return false
}
if isCodeMatch(e.ErrorCode(), xe.ErrorCode()) {
return true
}
return e == xe
}
return false
}
func (e *xError) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
writeFieldsToFormatState(s, e.fields)
if e.error != nil {
_, _ = fmt.Fprintf(s, "%+v\n", e.error)
} else {
chainMsg := e.chainMsg
if chainMsg == "" {
chainMsg = e.code.String()
}
_, _ = fmt.Fprintf(s, "%s\n", chainMsg)
}
if e.stack != nil {
_, _ = fmt.Fprintf(s, "%s", e.stack.String())
}
return
}
// %v: 与 Error() 一致,符合 Go 惯例
_, _ = fmt.Fprintf(s, "%s", e.Error())
case 's':
_, _ = fmt.Fprintf(s, "%s", e.Error())
case 'q':
_, _ = fmt.Fprintf(s, "%q", e.Error())
}
}
func (e *xError) WithFields(fields ...Field) XError {
if len(fields) == 0 {
return e
}
base := appendFields(e.errorBase, fields...)
return &xError{
errorBase: base,
error: e.error,
}
}
func (e *xError) WithHttpStatus(httpStatus int) XError {
base := e.errorBase
base.code = xcode.WithHttpStatus(base.code, httpStatus)
return &xError{errorBase: base, error: e.error}
}
type jsonError struct {
Code int `json:"code"`
HttpStatus int `json:"httpStatus"`
Message string `json:"message"`
Fields json.RawMessage `json:"fields,omitempty"`
}
func (e *xError) MarshalJSON() ([]byte, error) {
je := jsonError{
Code: e.ErrorCode(),
HttpStatus: e.HttpStatus(),
Message: e.Message(),
}
fieldsJSON, err := marshalFieldsJSON(e.fields)
if err != nil {
return nil, err
}
je.Fields = fieldsJSON
return json.Marshal(je)
}