forked from monzo/typhon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
errors.go
172 lines (148 loc) · 5.69 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
package errors
import (
"fmt"
"strings"
"github.com/mondough/typhon/errors/stack"
)
type Error struct {
Code string `json:"code"`
Message string `json:"message"`
Params map[string]string `json:"params"`
StackFrames stack.Stack `json:"stack"`
}
// Generic error codes. Each of these has their own constructor for convenience.
// You can use any string as a code, just use the `New` method.
const (
ErrUnknown = "unknown"
ErrInternalService = "internal_service"
ErrBadRequest = "bad_request"
ErrBadResponse = "bad_response"
ErrForbidden = "forbidden"
ErrUnauthorized = "unauthorized"
ErrNotFound = "not_found"
ErrTimeout = "timeout"
)
// Error returns a string message of the error. It is a concatenation of Code and Message params
// This means the Error implements the error interface
func (p *Error) Error() string {
if p == nil {
return ""
}
if p.Message == "" {
return p.Code
}
if p.Code == "" {
return p.Message
}
return fmt.Sprintf("%s: %s", p.Code, p.Message)
}
// StackString formats the stack as a beautiful string with newlines
func (p *Error) StackString() string {
stackStr := ""
for _, frame := range p.StackFrames {
stackStr = fmt.Sprintf("%s\n %s:%d in %s", stackStr, frame.Filename, frame.Line, frame.Method)
}
return stackStr
}
// VerboseString returns the error message, stack trace and params
func (p *Error) VerboseString() string {
return fmt.Sprintf("%s\nParams: %+v\n%s", p.Error(), p.Params, p.StackString())
}
func (p *Error) Format(f fmt.State, c rune) {
f.Write([]byte(p.Message))
}
// New creates a new error for you. Use this if you want to pass along a custom error code.
// Otherwise use the handy shorthand factories below
func New(code string, message string, params map[string]string) *Error {
return errorFactory(code, message, params)
}
// Wrap takes any error interface and wraps it into an Error.
// This is useful because an Error contains lots of useful goodies, like the stacktrace of the error.
// NOTE: If `err` is already an `Error` the passed params will be ignored
func Wrap(err error, params map[string]string) error {
return WrapWithCode(err, params, ErrInternalService)
}
func WrapWithCode(err error, params map[string]string, code string) error {
if err == nil {
return nil
}
switch err := err.(type) {
case *Error:
return err
default:
return errorFactory(code, err.Error(), params)
}
}
// InternalService creates a new error to represent an internal service error.
// Only use internal service error if we know very little about the error. Most
// internal service errors will come from `Wrap`ing a vanilla `error` interface
func InternalService(code, message string, params map[string]string) *Error {
return errorFactory(errCode(ErrInternalService, code), message, params)
}
// BadRequest creates a new error to represent an error caused by the client sending
// an invalid request. This is non-retryable unless the request is modified.
func BadRequest(code, message string, params map[string]string) *Error {
return errorFactory(errCode(ErrBadRequest, code), message, params)
}
// BadResponse creates a new error representing a failure to response with a valid response
// Examples of this would be a handler returning an invalid message format
func BadResponse(code, message string, params map[string]string) *Error {
return errorFactory(errCode(ErrBadResponse, code), message, params)
}
// Timeout creates a new error representing a timeout from client to server
func Timeout(code, message string, params map[string]string) *Error {
return errorFactory(errCode(ErrTimeout, code), message, params)
}
// NotFound creates a new error representing a resource that cannot be found. In some
// cases this is not an error, and would be better represented by a zero length slice of elements
func NotFound(code, message string, params map[string]string) *Error {
return errorFactory(errCode(ErrNotFound, code), message, params)
}
// Forbidden creates a new error representing a resource that cannot be accessed with
// the current authorisation credentials. The user may need authorising, or if authorised,
// may not be permitted to perform this action
func Forbidden(code, message string, params map[string]string) *Error {
return errorFactory(errCode(ErrForbidden, code), message, params)
}
// Unauthorized creates a new error indicating that authentication is required,
// but has either failed or not been provided.
func Unauthorized(code, message string, params map[string]string) *Error {
return errorFactory(errCode(ErrUnauthorized, code), message, params)
}
// errorConstructor returns a `*Error` with the specified code, message and params.
// Builds a stack based on the current call stack
func errorFactory(code string, message string, params map[string]string) *Error {
err := &Error{
Code: ErrUnknown,
Message: message,
Params: map[string]string{},
}
if len(code) > 0 {
err.Code = code
}
if params != nil {
err.Params = params
}
// TODO pass in context.Context
// Build stack and skip first three lines:
// - stack.go BuildStack()
// - errors.go errorFactory()
// - errors.go public constructor method
err.StackFrames = stack.BuildStack(3)
return err
}
func errCode(prefix, code string) string {
if code == "" {
return prefix
}
if prefix == "" {
return code
}
return strings.Join([]string{prefix, code}, ".")
}
// Matches returns whether the string returned from error.Error() contains the given param string. This means you can
// match the error on different levels e.g. dotted codes `bad_request` or `bad_request.missing_param` or even on the
// more descriptive message
func (p *Error) Matches(match string) bool {
return strings.Contains(p.Error(), match)
}