forked from attic-labs/noms
/
try.go
182 lines (156 loc) · 4.15 KB
/
try.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
// Copyright 2016 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
// Package d implements several debug, error and assertion functions used throughout Noms.
package d
import (
"errors"
"fmt"
"reflect"
"github.com/stretchr/testify/assert"
)
// d.Chk.<Method>() -- used in test cases and as assertions
var (
Chk = assert.New(&panicker{})
)
type panicker struct {
}
func (s panicker) Errorf(format string, args ...interface{}) {
panic(fmt.Sprintf(format, args...))
}
// Panic(err) creates an error using format and args and wraps it in a
// WrappedError which can be handled using Try() and TryCatch()
func Panic(format string, args ...interface{}) {
if len(args) == 0 {
err := errors.New(format)
panic(Wrap(err))
}
err := fmt.Errorf(format, args...)
panic(Wrap(err))
}
// PanicIfError(err) && PanicIfTrue(expr) can be used to panic in a way that's
// easily handled by Try() and TryCatch()
func PanicIfError(err error) {
if err != nil {
panic(Wrap(err))
}
}
// If b is true, creates a default error, wraps it and panics.
func PanicIfTrue(b bool) {
if b {
panic(Wrap(errors.New("Expected true")))
}
}
// If b is false, creates a default error, wraps it and panics.
func PanicIfFalse(b bool) {
if !b {
panic(Wrap(errors.New("Expected false")))
}
}
// If 'f' panics with a WrappedError then recover that error.
// If types is empty, return the WrappedError.
// if types is not empty and cause is not one of the listed types, re-panic.
// if types is not empty and cause is one of the types, return 'cause'
func Try(f func(), types ...interface{}) (err error) {
defer recoverWrappedTypes(&err, types)
f()
return
}
// If 'f' panics with a WrappedError then recover that error and return it.
// If types is empty, return the WrappedError.
// if types is not empty and cause is not one of the listed types, re-panic.
// if types is not empty and cause is one of the types, return 'cause'
func TryCatch(f func(), catch func(err error) error) (err error) {
defer recoverWrapped(&err, catch)
f()
return
}
type WrappedError interface {
Error() string
Cause() error
}
// Wraps an error. The enclosing error has a default Error() that contains the error msg along
// with a backtrace. The original error can be retrieved by calling err.Cause().
func Wrap(err error) WrappedError {
if err == nil {
return nil
}
if we, ok := err.(WrappedError); ok {
return we
}
st := stackTracer{}
assert := assert.New(&st)
assert.Fail(err.Error())
return wrappedError{st.stackTrace, err}
}
// If err is a WrappedError, then Cause() is returned, otherwise returns err.
func Unwrap(err error) error {
cause := err
we, ok := err.(WrappedError)
if ok {
cause = we.Cause()
}
return cause
}
func causeInTypes(err error, types ...interface{}) bool {
cause := Unwrap(err)
typ := reflect.TypeOf(cause)
for _, t := range types {
if typ == reflect.TypeOf(t) {
return true
}
}
return false
}
// Utility method, that checks type of error and panics with wrapped error not one of the listed types.
func PanicIfNotType(err error, types ...interface{}) error {
if err == nil {
return nil
}
if !causeInTypes(err, types...) {
we, ok := err.(WrappedError)
if !ok {
we = Wrap(err)
}
panic(we)
}
return Unwrap(err)
}
type wrappedError struct {
msg string
cause error
}
func (we wrappedError) Error() string { return we.msg }
func (we wrappedError) Cause() error { return we.cause }
type stackTracer struct {
stackTrace string
}
func (s *stackTracer) Errorf(format string, args ...interface{}) {
s.stackTrace = fmt.Sprintf(format, args...)
}
func recoverWrappedTypes(errp *error, types []interface{}) {
if r := recover(); r != nil {
if wrapper, ok := r.(wrappedError); !ok {
panic(r)
} else if len(types) > 0 && !causeInTypes(wrapper, types...) {
panic(r)
} else if len(types) > 0 {
*errp = wrapper.Cause()
} else {
*errp = wrapper
}
}
}
func recoverWrapped(errp *error, catch func(err error) error) {
if r := recover(); r != nil {
we, ok := r.(wrappedError)
if !ok {
panic(r)
}
if catch != nil {
*errp = catch(we)
} else {
*errp = Unwrap(we)
}
}
}