github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/infra/errcode/code.go (about) 1 package errcode 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "strings" 8 9 "github.com/jxskiss/gopkg/v2/internal/unsafeheader" 10 ) 11 12 // ErrCode is the interface implemented by an error code. 13 type ErrCode interface { 14 15 // Error returns the error message, it implements the error interface. 16 Error() string 17 18 // Code returns the integer error code. 19 Code() int32 20 21 // Message returns the registered message for the error code. 22 // If message is not available, it returns an empty string "". 23 Message() string 24 25 // Details returns the error details attached to the error. 26 // It returns nil if no details are attached. 27 Details() []any 28 } 29 30 // Code represents an error code. It can be created by calling 31 // Registry.Register or Registry.RegisterReserved. 32 // Code implements the ErrCode interface. 33 type Code struct { 34 code int32 35 msg string 36 details []any 37 reg *Registry 38 } 39 40 func (e *Code) String() string { return e.Error() } 41 42 func (e *Code) Format(f fmt.State, c rune) { 43 if c == 'v' && f.Flag('+') { 44 e.formatWithDetails(f) 45 } else { 46 errMsg := e.Error() 47 f.Write(unsafeheader.StringToBytes(errMsg)) 48 } 49 } 50 51 func (e *Code) formatWithDetails(w io.Writer) { 52 const ( 53 sep = "\n - " 54 indent = "\n " 55 ) 56 io.WriteString(w, e.Error()) 57 if len(e.details) > 0 { 58 io.WriteString(w, "\ndetails:") 59 for _, x := range e.details { 60 s := fmt.Sprintf("%+v", x) 61 s = strings.ReplaceAll(s, "\n", indent) 62 io.WriteString(w, sep) 63 io.WriteString(w, s) 64 } 65 } 66 } 67 68 // Error returns the error message, it implements the error interface. 69 // If message is not registered for the error code, it uses "unknown" 70 // as a default message. 71 func (e *Code) Error() string { 72 code := e.Code() 73 msg := e.Message() 74 if msg == "" { 75 msg = "unknown" 76 } 77 return fmt.Sprintf("[%d] %s", code, msg) 78 } 79 80 // Code returns the integer error code. 81 func (e *Code) Code() int32 { return e.code } 82 83 // Message returns the error message associated with the error code. 84 // If message is not available, it returns an empty string "". 85 func (e *Code) Message() string { 86 if e.msg != "" { 87 return e.msg 88 } 89 return e.reg.getMessage(e.code) 90 } 91 92 // Details returns the error details attached to the Code. 93 // It returns nil if no details are attached. 94 func (e *Code) Details() []any { return e.details } 95 96 func (e *Code) clone() *Code { 97 detailsLen := len(e.details) 98 return &Code{ 99 code: e.code, 100 msg: e.msg, 101 details: e.details[:detailsLen:detailsLen], 102 reg: e.reg, 103 } 104 } 105 106 // WithMessage returns a copy of Code with the given message. 107 func (e *Code) WithMessage(msg string) (code *Code) { 108 code = e.clone() 109 code.msg = msg 110 return 111 } 112 113 // AddDetails returns a copy of Code with new error details attached 114 // to the returned Code. 115 func (e *Code) AddDetails(details ...any) (code *Code) { 116 code = e.clone() 117 code.details = append(code.details, details...) 118 return 119 } 120 121 // RemoveDetails returns a copy of Code without the error details. 122 // If the Code does not have error details, it returns the Code 123 // directly instead of a copy. 124 // When returning an error code to end-users, you may want to remove 125 // the error details which generally should not be exposed to them. 126 func (e *Code) RemoveDetails() (code *Code) { 127 if len(e.details) == 0 { 128 return e 129 } 130 return &Code{code: e.code, msg: e.msg, reg: e.reg} 131 } 132 133 type jsonCode struct { 134 Code int32 `json:"code"` 135 Message string `json:"message,omitempty"` 136 Details []any `json:"details,omitempty"` 137 } 138 139 // MarshalJSON implements json.Marshaler. 140 func (e *Code) MarshalJSON() ([]byte, error) { 141 out := &jsonCode{ 142 Code: e.Code(), 143 Message: e.Message(), 144 Details: e.details, 145 } 146 return json.Marshal(out) 147 } 148 149 // UnmarshalJSON implements json.Unmarshaler. 150 func (e *Code) UnmarshalJSON(data []byte) error { 151 tmp := &jsonCode{} 152 err := json.Unmarshal(data, tmp) 153 if err != nil { 154 return err 155 } 156 e.code = tmp.Code 157 e.msg = tmp.Message 158 e.details = tmp.Details 159 return nil 160 } 161 162 // Is reports whether an error is ErrCode and the code is same. 163 // 164 // This method allows Code to be tested using errors.Is. 165 func (e *Code) Is(target error) bool { 166 if errCode, ok := target.(ErrCode); ok { 167 return e.code == errCode.Code() 168 } 169 return false 170 } 171 172 // Details returns the details attached to err if it is an ErrCode, 173 // it returns nil if err is not an ErrCode. 174 func Details(err error) []any { 175 if errCode := unwrapErrCode(err); errCode != nil { 176 return errCode.Details() 177 } 178 return nil 179 } 180 181 // Is reports whether any error in err's chain matches the target ErrCode. 182 func Is(err error, target ErrCode) bool { 183 errCode := unwrapErrCode(err) 184 return errCode != nil && errCode.Code() == target.Code() 185 } 186 187 // IsErrCode reports whether any error in err's chain is an ErrCode. 188 func IsErrCode(err error) bool { 189 if errCode := unwrapErrCode(err); errCode != nil { 190 return true 191 } 192 return false 193 } 194 195 func unwrapErrCode(err error) ErrCode { 196 type causer interface { 197 Cause() error 198 } 199 type wrapper interface { 200 Unwrap() error 201 } 202 203 // We make sure that a poor implementation that causes a cycle 204 // does not run forever. 205 const unwrapLimit = 100 206 207 for i := 0; err != nil && i < unwrapLimit; i++ { 208 if code, _ := err.(ErrCode); code != nil { 209 return code 210 } 211 switch e := err.(type) { 212 case causer: 213 err = e.Cause() 214 case wrapper: 215 err = e.Unwrap() 216 default: 217 return nil 218 } 219 } 220 return nil 221 }