File: errs.go

package info (click to toggle)
golang-github-zeebo-errs 1.3.0-3
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 116 kB
  • sloc: makefile: 2
file content (293 lines) | stat: -rw-r--r-- 7,150 bytes parent folder | download
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// Package errs provides a simple error package with stack traces.
package errs

import (
	"fmt"
	"io"
	"runtime"
)

// Namer is implemented by all errors returned in this package. It returns a
// name for the class of error it is, and a boolean indicating if the name is
// valid.
type Namer interface{ Name() (string, bool) }

// Causer is implemented by all errors returned in this package. It returns
// the underlying cause of the error, or nil if there is no underlying cause.
type Causer interface{ Cause() error }

// unwrapper is implemented by all errors returned in this package. It returns
// the underlying cause of the error, or nil if there is no underlying error.
type unwrapper interface{ Unwrap() error }

// ungrouper is implemented by combinedError returned in this package. It
// returns all underlying errors, or nil if there is no underlying error.
type ungrouper interface{ Ungroup() []error }

// New returns an error not contained in any class. This is the same as calling
// fmt.Errorf(...) except it captures a stack trace on creation.
func New(format string, args ...interface{}) error {
	return (*Class).create(nil, 3, fmt.Errorf(format, args...))
}

// Wrap returns an error not contained in any class. It just associates a stack
// trace with the error. Wrap returns nil if err is nil.
func Wrap(err error) error {
	return (*Class).create(nil, 3, err)
}

// WrapP stores into the error pointer if it contains a non-nil error an error not
// contained in any class. It just associates a stack trace with the error. WrapP
// does nothing if the pointer or pointed at error is nil.
func WrapP(err *error) {
	if err != nil && *err != nil {
		*err = (*Class).create(nil, 3, *err)
	}
}

// Often, we call Cause as much as possible. Since comparing arbitrary
// interfaces with equality isn't panic safe, we only loop up to 100
// times to ensure that a poor implementation that causes a cycle does
// not run forever.
const maxCause = 100

// Unwrap returns the underlying error, if any, or just the error.
func Unwrap(err error) error {
	for i := 0; err != nil && i < maxCause; i++ {
		var nerr error

		switch e := err.(type) {
		case Causer:
			nerr = e.Cause()

		case unwrapper:
			nerr = e.Unwrap()
		}

		if nerr == nil {
			return err
		}
		err = nerr
	}

	return err
}

// Classes returns all the classes that have wrapped the error.
func Classes(err error) (classes []*Class) {
	causes := 0
	for {
		switch e := err.(type) {
		case *errorT:
			if e.class != nil {
				classes = append(classes, e.class)
			}
			err = e.err
			continue

		case Causer:
			err = e.Cause()

		case unwrapper:
			err = e.Unwrap()

		default:
			return classes
		}

		if causes >= maxCause {
			return classes
		}
		causes++
	}
}

// Is checks if any of the underlying errors matches target
func Is(err, target error) bool {
	return IsFunc(err, func(err error) bool {
		return err == target
	})
}

// IsFunc checks if any of the underlying errors matches the func
func IsFunc(err error, is func(err error) bool) bool {
	causes := 0
	errs := []error{err}

	for len(errs) > 0 {
		var next []error
		for _, err := range errs {
			if is(err) {
				return true
			}

			switch e := err.(type) {
			case ungrouper:
				ungrouped := e.Ungroup()
				for _, unerr := range ungrouped {
					if unerr != nil {
						next = append(next, unerr)
					}
				}
			case Causer:
				cause := e.Cause()
				if cause != nil {
					next = append(next, cause)
				}
			case unwrapper:
				unwrapped := e.Unwrap()
				if unwrapped != nil {
					next = append(next, unwrapped)
				}
			}

			if causes >= maxCause {
				return false
			}
			causes++
		}
		errs = next
	}

	return false
}

//
// error classes
//

// Class represents a class of errors. You can construct errors, and check if
// errors are part of the class.
type Class string

// Has returns true if the passed in error was wrapped by this class.
func (c *Class) Has(err error) bool {
	return IsFunc(err, func(err error) bool {
		errt, ok := err.(*errorT)
		return ok && errt.class == c
	})
}

// New constructs an error with the format string that will be contained by
// this class. This is the same as calling Wrap(fmt.Errorf(...)).
func (c *Class) New(format string, args ...interface{}) error {
	return c.create(3, fmt.Errorf(format, args...))
}

// Wrap returns a new error based on the passed in error that is contained in
// this class. Wrap returns nil if err is nil.
func (c *Class) Wrap(err error) error {
	return c.create(3, err)
}

// WrapP stores into the error pointer if it contains a non-nil error an error contained
// in this class. WrapP does nothing if the pointer or pointed at error is nil.
func (c *Class) WrapP(err *error) {
	if err != nil && *err != nil {
		*err = c.create(3, *err)
	}
}

// create constructs the error, or just adds the class to the error, keeping
// track of the stack if it needs to construct it.
func (c *Class) create(depth int, err error) error {
	if err == nil {
		return nil
	}

	var pcs []uintptr
	if err, ok := err.(*errorT); ok {
		if c == nil || err.class == c {
			return err
		}
		pcs = err.pcs
	}

	errt := &errorT{
		class: c,
		err:   err,
		pcs:   pcs,
	}

	if errt.pcs == nil {
		errt.pcs = make([]uintptr, 64)
		n := runtime.Callers(depth, errt.pcs)
		errt.pcs = errt.pcs[:n:n]
	}

	return errt
}

//
// errors
//

// errorT is the type of errors returned from this package.
type errorT struct {
	class *Class
	err   error
	pcs   []uintptr
}

var ( // ensure *errorT implements the helper interfaces.
	_ Namer  = (*errorT)(nil)
	_ Causer = (*errorT)(nil)
	_ error  = (*errorT)(nil)
)

// Stack returns the pcs for the stack trace associated with the error.
func (e *errorT) Stack() []uintptr { return e.pcs }

// errorT implements the error interface.
func (e *errorT) Error() string {
	return fmt.Sprintf("%v", e)
}

// Format handles the formatting of the error. Using a "+" on the format string
// specifier will also write the stack trace.
func (e *errorT) Format(f fmt.State, c rune) {
	sep := ""
	if e.class != nil && *e.class != "" {
		fmt.Fprintf(f, "%s", string(*e.class))
		sep = ": "
	}
	if text := e.err.Error(); len(text) > 0 {
		fmt.Fprintf(f, "%s%v", sep, text)
	}
	if f.Flag(int('+')) {
		summarizeStack(f, e.pcs)
	}
}

// Cause implements the interface wrapping errors are expected to implement
// to allow getting at underlying causes.
func (e *errorT) Cause() error {
	return e.err
}

// Unwrap implements the draft design for error inspection. Since this is
// on an unexported type, it should not be hard to maintain going forward
// given that it also is the exact same semantics as Cause.
func (e *errorT) Unwrap() error {
	return e.err
}

// Name returns the name for the error, which is the first wrapping class.
func (e *errorT) Name() (string, bool) {
	if e.class == nil {
		return "", false
	}
	return string(*e.class), true
}

// summarizeStack writes stack line entries to the writer.
func summarizeStack(w io.Writer, pcs []uintptr) {
	frames := runtime.CallersFrames(pcs)
	for {
		frame, more := frames.Next()
		if !more {
			return
		}
		fmt.Fprintf(w, "\n\t%s:%d", frame.Function, frame.Line)
	}
}