-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy patherrors.go
194 lines (167 loc) · 5.16 KB
/
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
// Package errors helps in wrapping errors with custom type as well as a user friendly message. This is particularly useful when responding to APIs
package errors
import (
"net/http"
"runtime"
"strings"
)
type errType int
func (e errType) Int() int {
return int(e)
}
// While adding a new Type, the respective helper functions should be added, also update the
// WriteHTTP method accordingly
const (
// TypeInternal is error type for when there is an internal system error. e.g. Database errors
TypeInternal errType = iota
// TypeValidation is error type for when there is a validation error. e.g. invalid email address
TypeValidation
// TypeInputBody is error type for when an input data type error. e.g. invalid JSON
TypeInputBody
// TypeDuplicate is error type for when there's duplicate content
TypeDuplicate
// TypeUnauthenticated is error type when trying to access an authenticated API without authentication
TypeUnauthenticated
// TypeUnauthorized is error type for when there's an unauthorized access attempt
TypeUnauthorized
// TypeEmpty is error type for when an expected non-empty resource, is empty
TypeEmpty
// TypeNotFound is error type for an expected resource is not found e.g. user ID not found
TypeNotFound
// TypeMaximumAttempts is error type for attempting the same action more than allowed
TypeMaximumAttempts
// TypeSubscriptionExpired is error type for when a user's 'paid' account has expired
TypeSubscriptionExpired
// TypeDownstreamDependencyTimedout is error type for when a request to a downstream dependent service times out
TypeDownstreamDependencyTimedout
// DefaultMessage is the default user friendly message
DefaultMessage = "unknown error occurred"
)
var (
defaultErrType = TypeInternal
)
// Error is the struct which holds custom attributes
type Error struct {
// original is the original error
original error
// Message is meant to be returned as response of API, so this should be a user-friendly message
message string
// Type is used to define the type of the error, e.g. Server error, validation error etc.
eType errType
fileLine string
}
// Error is the implementation of error interface
func (e *Error) Error() string {
if e.original != nil {
// string concatenation with + is ~100x faster than fmt.Sprintf()
return e.fileLine + " " + e.original.Error()
/*
// use the following code instead of the above return, to avoid nested filename:line
err, _ := e.original.(*Error)
if err != nil {
return err.Error()
}
return fmt.Sprintf("%s %s", e.fileLine, e.original.Error())
*/
}
if e.message != "" {
// string concatenation with + is ~100x faster than fmt.Sprintf()
return e.fileLine + " " + e.message
}
// string concatenation with + is ~100x faster than fmt.Sprintf()
return e.fileLine + " " + DefaultMessage
}
// Message returns the user friendly message stored in the error struct. It will ignore all errors
// which are not of type *Error
func (e *Error) Message() string {
messages := make([]string, 0, 5)
if e.message != "" {
messages = append(messages, e.message)
}
err, _ := e.original.(*Error)
for err != nil {
if err.message == "" {
err, _ = err.original.(*Error)
continue
}
messages = append(messages, err.message)
err, _ = err.original.(*Error)
}
if len(messages) > 0 {
return strings.Join(messages, ". ")
}
return e.Error()
}
// Unwrap implement's Go 1.13's Unwrap interface exposing the wrapped error
func (e *Error) Unwrap() error {
return e.original
}
// Is implements the Is interface required by Go
func (e *Error) Is(err error) bool {
o, _ := err.(*Error)
return o != nil && o == e
}
// HTTPStatusCode is a convenience method used to get the appropriate HTTP response status code for the respective error type
func (e *Error) HTTPStatusCode() int {
status := http.StatusInternalServerError
switch e.eType {
case TypeValidation:
{
status = http.StatusUnprocessableEntity
}
case TypeInputBody:
{
status = http.StatusBadRequest
}
case TypeDuplicate:
{
status = http.StatusConflict
}
case TypeUnauthenticated:
{
status = http.StatusUnauthorized
}
case TypeUnauthorized:
{
status = http.StatusForbidden
}
case TypeEmpty:
{
status = http.StatusGone
}
case TypeNotFound:
{
status = http.StatusNotFound
}
case TypeMaximumAttempts:
{
status = http.StatusTooManyRequests
}
case TypeSubscriptionExpired:
{
status = http.StatusPaymentRequired
}
}
return status
}
// Type returns the error type as integer
func (e *Error) Type() errType {
return e.eType
}
// New returns a new instance of Error with the relavant fields initialized
func New(msg string) *Error {
_, file, line, _ := runtime.Caller(1)
return newerr(nil, msg, file, line, defaultErrType)
}
func Newf(fromat string, args ...interface{}) *Error {
_, file, line, _ := runtime.Caller(1)
return newerrf(nil, file, line, defaultErrType, fromat, args...)
}
func Errorf(fromat string, args ...interface{}) *Error {
_, file, line, _ := runtime.Caller(1)
return newerrf(nil, file, line, defaultErrType, fromat, args...)
}
// SetDefaultType will set the default error type, which is used in the 'New' function
func SetDefaultType(e errType) {
defaultErrType = e
}