github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/kit/statusx/error.go (about) 1 package statusx 2 3 import ( 4 "bytes" 5 "fmt" 6 "net/http" 7 "regexp" 8 "sort" 9 "strconv" 10 "strings" 11 12 "github.com/pkg/errors" 13 ) 14 15 type Error interface { 16 StatusErr() *StatusErr 17 Error() string 18 } 19 20 type ServiceCode interface { 21 ServiceCode() int 22 } 23 24 func IsStatusErr(err error) (*StatusErr, bool) { 25 if err == nil { 26 return nil, false 27 } 28 29 if e, ok := err.(Error); ok { 30 return e.StatusErr(), ok 31 } 32 33 se, ok := err.(*StatusErr) 34 return se, ok 35 } 36 37 func FromErr(err error) *StatusErr { 38 if err == nil { 39 return nil 40 } 41 if se, ok := IsStatusErr(err); ok { 42 return se 43 } 44 return NewUnknownErr().WithDesc(err.Error()) 45 } 46 47 func Wrap(err error, code int, key string, msgs ...string) *StatusErr { 48 if err == nil { 49 return nil 50 } 51 52 if len(strconv.Itoa(code)) == 3 { 53 code = code * 1e6 54 } 55 56 msg := key 57 58 if len(msgs) > 0 { 59 msg = msgs[0] 60 } 61 62 desc := "" 63 64 if len(msgs) > 1 { 65 desc = strings.Join(msgs[1:], "\n") 66 } else { 67 desc = err.Error() 68 } 69 70 // err = errors.WithMessage(err, "asdfasdfasdfasdfass") 71 s := &StatusErr{ 72 Key: key, 73 Code: code, 74 Msg: msg, 75 Desc: desc, 76 error: errors.WithStack(err), 77 } 78 79 return s 80 } 81 82 func NewUnknownErr() *StatusErr { 83 return NewStatusErr("UnknownError", http.StatusInternalServerError*1e6, "unknown error") 84 } 85 86 func NewStatusErr(key string, code int, msg string) *StatusErr { 87 return &StatusErr{ 88 Key: key, 89 Code: code, 90 Msg: msg, 91 } 92 } 93 94 type StatusErr struct { 95 Key string `json:"key" xml:"key"` // key of err 96 Code int `json:"code" xml:"code"` // unique err code 97 Msg string `json:"msg" xml:"msg"` // msg of err 98 Desc string `json:"desc" xml:"desc"` // desc of err 99 CanBeTalk bool `json:"canBeTalk" xml:"canBeTalk"` // can be task error; for client to should error msg to end user 100 ID string `json:"id" xml:"id"` // request ID or other request context 101 Sources []string `json:"sources" xml:"sources"` // error tracing 102 Fields ErrorFields `json:"fields" xml:"fields"` // error in where fields 103 error error 104 } 105 106 // @err[UnknownError][500000000][unknown error] 107 var regexpStatusErrSummary = regexp.MustCompile(`@StatusErr\[(.+)\]\[(.+)\]\[(.+)\](!)?`) 108 109 func ParseStatusErrSummary(s string) (*StatusErr, error) { 110 if !regexpStatusErrSummary.Match([]byte(s)) { 111 return nil, fmt.Errorf("unsupported status err summary: %s", s) 112 } 113 114 matched := regexpStatusErrSummary.FindStringSubmatch(s) 115 116 code, _ := strconv.ParseInt(matched[2], 10, 64) 117 118 return &StatusErr{ 119 Key: matched[1], 120 Code: int(code), 121 Msg: matched[3], 122 CanBeTalk: matched[4] != "", 123 }, nil 124 } 125 126 func (se *StatusErr) Summary() string { 127 s := fmt.Sprintf( 128 `@StatusErr[%s][%d][%s]`, 129 se.Key, 130 se.Code, 131 se.Msg, 132 ) 133 134 if se.CanBeTalk { 135 return s + "!" 136 } 137 return s 138 } 139 140 func (se *StatusErr) Is(err error) bool { 141 e := FromErr(err) 142 if se == nil || e == nil { 143 return false 144 } 145 return e.Key == se.Key && e.Code == se.Code 146 } 147 148 func StatusCodeFromCode(code int) int { 149 strCode := fmt.Sprintf("%d", code) 150 if len(strCode) < 3 { 151 return 0 152 } 153 statusCode, _ := strconv.Atoi(strCode[:3]) 154 return statusCode 155 } 156 157 func (se *StatusErr) StatusCode() int { 158 return StatusCodeFromCode(se.Code) 159 } 160 161 func (se *StatusErr) Error() string { 162 s := fmt.Sprintf( 163 "[%s]%s%s", 164 strings.Join(se.Sources, ","), 165 se.Summary(), 166 se.Fields, 167 ) 168 169 if se.Desc != "" { 170 s += " " + se.Desc 171 } 172 173 return s 174 } 175 176 func (se StatusErr) WithMsg(msg string) *StatusErr { 177 se.Msg = msg 178 return &se 179 } 180 181 func (se StatusErr) WithDesc(desc string) *StatusErr { 182 se.Desc = desc 183 return &se 184 } 185 186 func (se StatusErr) WithID(id string) *StatusErr { 187 se.ID = id 188 return &se 189 } 190 191 func (se StatusErr) AppendSource(sourceName string) *StatusErr { 192 length := len(se.Sources) 193 if length == 0 || se.Sources[length-1] != sourceName { 194 se.Sources = append(se.Sources, sourceName) 195 } 196 return &se 197 } 198 199 func (se StatusErr) EnableErrTalk() *StatusErr { 200 se.CanBeTalk = true 201 return &se 202 } 203 204 func (se StatusErr) DisableErrTalk() *StatusErr { 205 se.CanBeTalk = false 206 return &se 207 } 208 209 func (se StatusErr) AppendErrorField(in string, field string, msg string) *StatusErr { 210 se.Fields = append(se.Fields, NewErrorField(in, field, msg)) 211 return &se 212 } 213 214 func (se StatusErr) AppendErrorFields(errorFields ...*ErrorField) *StatusErr { 215 se.Fields = append(se.Fields, errorFields...) 216 return &se 217 } 218 219 func NewErrorField(in string, field string, msg string) *ErrorField { 220 return &ErrorField{ 221 In: in, 222 Field: field, 223 Msg: msg, 224 } 225 } 226 227 type ErrorField struct { 228 Field string `json:"field" xml:"field"` // Field path: prop.slice[2].a 229 Msg string `json:"msg" xml:"msg"` // Msg message 230 In string `json:"in" xml:"in"` // In location eq. body, query, header, path, formData 231 } 232 233 func (s ErrorField) String() string { 234 return s.Field + " in " + s.In + " - " + s.Msg 235 } 236 237 type ErrorFields []*ErrorField 238 239 func (fs ErrorFields) String() string { 240 if len(fs) == 0 { 241 return "" 242 } 243 244 sort.Sort(fs) 245 246 buf := &bytes.Buffer{} 247 buf.WriteString("<") 248 for i, f := range fs { 249 if i > 0 { 250 buf.WriteString(", ") 251 } 252 buf.WriteString(f.String()) 253 } 254 buf.WriteString(">") 255 return buf.String() 256 } 257 258 func (fs ErrorFields) Len() int { 259 return len(fs) 260 } 261 262 func (fs ErrorFields) Swap(i, j int) { 263 fs[i], fs[j] = fs[j], fs[i] 264 } 265 266 func (fs ErrorFields) Less(i, j int) bool { 267 return fs[i].Field < fs[j].Field 268 }