File: errors.go

package info (click to toggle)
golang-github-efficientgo-core 1.0.0~rc3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 580 kB
  • sloc: makefile: 68
file content (168 lines) | stat: -rw-r--r-- 5,626 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
// Copyright (c) The EfficientGo Authors.
// Licensed under the Apache License 2.0.

// Initially copied from Thanos and contributed by https://github.com/bisakhmondal.
//
// Copyright (c) The Thanos Authors.
// Licensed under the Apache License 2.0.

package errors

import (
	"errors"
	"fmt"
	"strings"
)

// base is the fundamental struct that implements the error interface and the acts as the backbone of this errors package.
type base struct {
	// info contains the error message passed through calls like errors.Wrap, errors.New.
	info string
	// stacktrace stores information about the program counters - i.e. where this error was generated.
	stack stacktrace
	// err is the actual error which is being wrapped with a stacktrace and message information.
	err error
}

// Error implements the error interface.
func (b *base) Error() string {
	if b.err != nil {
		return fmt.Sprintf("%s: %s", b.info, b.err.Error())
	}
	return b.info
}

// Unwrap implements the error Unwrap interface.
func (b *base) Unwrap() error {
	return b.err
}

// Format implements the fmt.Formatter interface to support the formatting of an error chain with "%+v" verb.
// Whenever error is printed with %+v format verb, stacktrace info gets dumped to the output.
func (b *base) Format(s fmt.State, verb rune) {
	if verb == 'v' && s.Flag('+') {
		s.Write([]byte(formatErrorChain(b)))
		return
	}

	s.Write([]byte(b.Error()))
}

// New returns a new error with a stacktrace of recent call frames. Each call to New
// returns a distinct error value even if the text is identical. An alternative of
// the errors.New function from github.com/pkg/errors.
func New(message string) error {
	return &base{
		info:  message,
		stack: newStackTrace(),
		err:   nil,
	}
}

// Newf is like New, but it formats input according to a format specifier.
// An alternative of the fmt.Errorf function.
//
// If no args have been passed, it is same as `New` function without formatting. Character like
// '%' still has to be escaped in that scenario.
func Newf(format string, args ...interface{}) error {
	return &base{
		info:  fmt.Sprintf(format, args...),
		stack: newStackTrace(),
		err:   nil,
	}
}

// Wrap returns a new error, which wraps another error with a stacktrace containing recent call frames.
//
// If cause is nil, it does not produce the error, similar to errors.Wrap from github.com/pkg/errors was doing.
// Still, avoid `errors.Wrap(nil, "...") patterns as it can lead to inefficiencies while constructing arguments
// to format as well readability issues. We keep this behaviour to make sure it's a drop-in replacement.
func Wrap(cause error, message string) error {
	if cause == nil {
		return nil
	}

	return &base{
		info:  message,
		stack: newStackTrace(),
		err:   cause,
	}
}

// Wrapf is like Wrap but the message is formatted with the supplied format specifier.
//
// If no args have been passed, it is same as `Wrap` function without formatting.
// Character like '%' still has to be escaped in that scenario.
func Wrapf(cause error, format string, args ...interface{}) error {
	if cause == nil {
		return nil
	}
	return &base{
		info:  fmt.Sprintf(format, args...),
		stack: newStackTrace(),
		err:   cause,
	}
}

// Cause returns the result of repeatedly calling the Unwrap method on err, if err's
// type implements an Unwrap method. Otherwise, Cause returns the last encountered error.
// The difference between Unwrap and Cause is the first one performs unwrapping of one level
// but Cause returns the last err (whether it's nil or not) where it failed to assert
// the interface containing the Unwrap method.
// This is a replacement of errors.Cause without the causer interface from pkg/errors which
// actually can be sufficed through the errors.Is function. But considering some use cases
// where we need to peel off all the external layers applied through errors.Wrap family,
// it is useful ( where external SDK doesn't use errors.Is internally).
func Cause(err error) error {
	for err != nil {
		e, ok := err.(interface {
			Unwrap() error
		})
		if !ok {
			return err
		}
		err = e.Unwrap()
	}
	return nil
}

// formatErrorChain formats an error chain.
func formatErrorChain(err error) string {
	var buf strings.Builder
	for err != nil {
		if e, ok := err.(*base); ok {
			buf.WriteString(fmt.Sprintf("%s\n%v", e.info, e.stack))
			err = e.err
		} else {
			buf.WriteString(fmt.Sprintf("%s\n", err.Error()))
			err = nil
		}
	}
	return buf.String()
}

// The functions `Is`, `As` & `Unwrap` provides a thin wrapper around the builtin errors
// package in go. Just for sake of completeness and correct autocompletion behaviors from
// IDEs they have been wrapped using functions instead of using variable to reference them
// as first class functions (eg: var Is = errros.Is ).

// Is is a wrapper of built-in errors.Is. It reports whether any error in err's
// chain matches target. The chain consists of err itself followed by the sequence
// of errors obtained by repeatedly calling Unwrap.
func Is(err, target error) bool {
	return errors.Is(err, target)
}

// As is a wrapper of built-in errors.As. It finds the first error in err's
// chain that matches target, and if one is found, sets target to that error
// value and returns true. Otherwise, it returns false.
func As(err error, target interface{}) bool {
	return errors.As(err, target)
}

// Unwrap is a wrapper of built-in errors.Unwrap. Unwrap returns the result of
// calling the Unwrap method on err, if err's type contains an Unwrap method
// returning error. Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
	return errors.Unwrap(err)
}