|
| 1 | +package errs |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "fmt" |
| 6 | + "runtime" |
| 7 | + "strings" |
| 8 | + |
| 9 | + errs "errors" |
| 10 | + |
| 11 | + "github.com/pkg/errors" |
| 12 | +) |
| 13 | + |
| 14 | +// UserName is a string representing a user |
| 15 | +type UserName string |
| 16 | + |
| 17 | +// Kind defines the kind of error this is |
| 18 | +type Kind uint8 |
| 19 | + |
| 20 | +// Code is a human-readable, short representation of the error |
| 21 | +type Code string |
| 22 | + |
| 23 | +// Parameter represents the parameter related to the error. |
| 24 | +type Parameter string |
| 25 | + |
| 26 | +// Error is the type that implements the error interface. |
| 27 | +// It contains a number of fields, each of different type. |
| 28 | +// An Error value may leave some values unset. |
| 29 | +type Error struct { |
| 30 | + // User is the username of the user attempting the operation. |
| 31 | + User UserName |
| 32 | + |
| 33 | + // Kind is the class of error, such as permission failure, |
| 34 | + // or "Other" if its class is unknown or irrelevant. |
| 35 | + Kind Kind |
| 36 | + |
| 37 | + // Code is a human-readable, short representation of the error |
| 38 | + Code Code |
| 39 | + |
| 40 | + // Param represents the parameter related to the error. |
| 41 | + Param Parameter |
| 42 | + |
| 43 | + // The underlying error that triggered this one, if any. |
| 44 | + Err error |
| 45 | +} |
| 46 | + |
| 47 | +// Is is method to satisfy errors.Is interface |
| 48 | +func (e *Error) Is(target error) bool { |
| 49 | + return errs.Is(e.Err, target) |
| 50 | +} |
| 51 | + |
| 52 | +// As is method to satisfy errors.As interface |
| 53 | +func (w *Error) As(target interface{}) bool { |
| 54 | + return errs.As(w.Err, target) |
| 55 | +} |
| 56 | + |
| 57 | +func (e *Error) Cause() error { |
| 58 | + return e.Err |
| 59 | +} |
| 60 | + |
| 61 | +func (e Error) Unwrap() error { |
| 62 | + return e.Err |
| 63 | +} |
| 64 | + |
| 65 | +func (e *Error) Error() string { |
| 66 | + return e.Err.Error() |
| 67 | +} |
| 68 | + |
| 69 | +// StackTrace satisfy errors.StackTrace interface |
| 70 | +func (e Error) StackTrace() errors.StackTrace { |
| 71 | + type stackTracer interface { |
| 72 | + StackTrace() errors.StackTrace |
| 73 | + } |
| 74 | + |
| 75 | + if st, ok := e.Err.(stackTracer); ok { |
| 76 | + return st.StackTrace() |
| 77 | + } |
| 78 | + |
| 79 | + return nil |
| 80 | +} |
| 81 | + |
| 82 | +type ValidationErrors []error |
| 83 | + |
| 84 | +func (v *ValidationErrors) Append(args ...interface{}) { |
| 85 | + *v = append(*v, E(args...)) |
| 86 | +} |
| 87 | + |
| 88 | +func (v ValidationErrors) Error() string { |
| 89 | + buff := bytes.NewBufferString("") |
| 90 | + |
| 91 | + for i := 0; i < len(v); i++ { |
| 92 | + |
| 93 | + if err, ok := v[i].(*Error); ok { |
| 94 | + buff.WriteString(fmt.Sprintf("%s: %s", err.Param, err.Error())) |
| 95 | + buff.WriteString("\n") |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + return strings.TrimSpace(buff.String()) |
| 100 | +} |
| 101 | + |
| 102 | +const ( |
| 103 | + Unknown Kind = iota // Unknown error. This value is expected for unknown error |
| 104 | + Other // Unclassified error. This value is not printed in the error message |
| 105 | + IO // External I/O error such as network failure |
| 106 | + Private // Information withheld |
| 107 | + Internal // Internal error or inconsistency |
| 108 | + Database // Database error |
| 109 | + Exist // Resource already exist |
| 110 | + NotExist // Resource does not exists |
| 111 | + Invalid // Invalid operation for this type of item |
| 112 | + Validation // Input validation error |
| 113 | + InvalidRequest // Invalid request |
| 114 | + Permission // Permission error request |
| 115 | + Unauthenticated // Unauthenticated error if unauthenticated request occur |
| 116 | +) |
| 117 | + |
| 118 | +func (k Kind) String() string { |
| 119 | + switch k { |
| 120 | + case Other: |
| 121 | + return "other_error" |
| 122 | + case IO: |
| 123 | + return "I/O_error" |
| 124 | + case Private: |
| 125 | + return "private" |
| 126 | + case Internal: |
| 127 | + return "internal_error" |
| 128 | + case Database: |
| 129 | + return "database_error" |
| 130 | + case Exist: |
| 131 | + return "resource_already_exists" |
| 132 | + case NotExist: |
| 133 | + return "resource_does_not_exist" |
| 134 | + case Invalid: |
| 135 | + return "invalid_operation" |
| 136 | + case Validation: |
| 137 | + return "input_validation_error" |
| 138 | + case InvalidRequest: |
| 139 | + return "invalid_request_error" |
| 140 | + case Permission: |
| 141 | + return "permission_denied" |
| 142 | + case Unauthenticated: |
| 143 | + return "unauthenticated_request" |
| 144 | + } |
| 145 | + |
| 146 | + return "unknown_error" |
| 147 | +} |
| 148 | + |
| 149 | +func Match(err1, err2 error) bool { |
| 150 | + e1, ok := err1.(*Error) |
| 151 | + if !ok { |
| 152 | + return false |
| 153 | + } |
| 154 | + e2, ok := err2.(*Error) |
| 155 | + if !ok { |
| 156 | + return false |
| 157 | + } |
| 158 | + if e1.User != "" && e2.User != e1.User { |
| 159 | + return false |
| 160 | + } |
| 161 | + if e1.Kind != Other && e2.Kind != e1.Kind { |
| 162 | + return false |
| 163 | + } |
| 164 | + if e1.Param != "" && e2.Param != e1.Param { |
| 165 | + return false |
| 166 | + } |
| 167 | + if e1.Code != "" && e2.Code != e1.Code { |
| 168 | + return false |
| 169 | + } |
| 170 | + if e1.Err != nil { |
| 171 | + if _, ok := e1.Err.(*Error); ok { |
| 172 | + return Match(e1.Err, e2.Err) |
| 173 | + } |
| 174 | + if e2.Err == nil || e2.Err.Error() != e1.Err.Error() { |
| 175 | + return false |
| 176 | + } |
| 177 | + } |
| 178 | + return true |
| 179 | +} |
| 180 | + |
| 181 | +func GetKind(err error) Kind { |
| 182 | + e, ok := err.(*Error) |
| 183 | + if !ok { |
| 184 | + return Unknown |
| 185 | + } |
| 186 | + |
| 187 | + return e.Kind |
| 188 | +} |
| 189 | + |
| 190 | +func E(args ...interface{}) error { |
| 191 | + type stackTracer interface { |
| 192 | + StackTrace() errors.StackTrace |
| 193 | + } |
| 194 | + |
| 195 | + if len(args) == 0 { |
| 196 | + panic("call to errors.E with no arguments") |
| 197 | + } |
| 198 | + |
| 199 | + e := &Error{} |
| 200 | + for _, arg := range args { |
| 201 | + switch arg := arg.(type) { |
| 202 | + case Kind: |
| 203 | + e.Kind = arg |
| 204 | + case UserName: |
| 205 | + e.User = arg |
| 206 | + case Code: |
| 207 | + e.Code = arg |
| 208 | + case Parameter: |
| 209 | + e.Param = arg |
| 210 | + case string: |
| 211 | + e.Err = errors.New(arg) |
| 212 | + case *Error: |
| 213 | + e.Err = arg |
| 214 | + case error: |
| 215 | + // if the error is validation errors, skipping the stacktrace |
| 216 | + if verr, ok := arg.(ValidationErrors); ok { |
| 217 | + e.Err = verr |
| 218 | + continue |
| 219 | + } |
| 220 | + |
| 221 | + // if the error implements stackTracer, then it is |
| 222 | + // a pkg/errors error type and does not need to have |
| 223 | + // the stack added |
| 224 | + _, ok := arg.(stackTracer) |
| 225 | + if ok { |
| 226 | + e.Err = arg |
| 227 | + } else { |
| 228 | + e.Err = errors.WithStack(arg) |
| 229 | + } |
| 230 | + default: |
| 231 | + _, file, line, _ := runtime.Caller(1) |
| 232 | + return fmt.Errorf("errors.E: bad call from %s:%d: %v, unknown type %T, value %v in error call", file, line, args, arg, arg) |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + prev, ok := e.Err.(*Error) |
| 237 | + if !ok { |
| 238 | + return e |
| 239 | + } |
| 240 | + // If this error has Kind unset or Other, pull up the inner one. |
| 241 | + if e.Kind == Other { |
| 242 | + e.Kind = prev.Kind |
| 243 | + prev.Kind = Other |
| 244 | + } |
| 245 | + |
| 246 | + if prev.Code == e.Code { |
| 247 | + prev.Code = "" |
| 248 | + } |
| 249 | + // If this error has Code == "", pull up the inner one. |
| 250 | + if e.Code == "" { |
| 251 | + e.Code = prev.Code |
| 252 | + prev.Code = "" |
| 253 | + } |
| 254 | + |
| 255 | + if prev.Param == e.Param { |
| 256 | + prev.Param = "" |
| 257 | + } |
| 258 | + // If this error has Code == "", pull up the inner one. |
| 259 | + if e.Param == "" { |
| 260 | + e.Param = prev.Param |
| 261 | + prev.Param = "" |
| 262 | + } |
| 263 | + |
| 264 | + return e |
| 265 | +} |
0 commit comments