aboutsummaryrefslogtreecommitdiffstats
path: root/library/go/core/xerrors/errorf.go
blob: 0ed8541f286938a45e6d92f0b489cc246ae389f3 (plain) (blame)
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
package xerrors

import (
	"fmt"
	"io"
	"strings"

	"github.com/ydb-platform/ydb/library/go/x/xruntime"
)

type wrappedErrorf struct {
	err        error
	stacktrace *xruntime.StackTrace
}

var _ ErrorStackTrace = &wrappedErrorf{}

func Errorf(format string, a ...interface{}) error {
	err := fmt.Errorf(format, a...)
	return &wrappedErrorf{
		err:        err,
		stacktrace: newStackTrace(1, err),
	}
}

func SkipErrorf(skip int, format string, a ...interface{}) error {
	err := fmt.Errorf(format, a...)
	return &wrappedErrorf{
		err:        err,
		stacktrace: newStackTrace(skip+1, err),
	}
}

func (e *wrappedErrorf) Format(s fmt.State, v rune) {
	switch v {
	case 'v':
		if s.Flag('+') {
			msg := e.err.Error()
			inner := Unwrap(e.err)
			// If Errorf wrapped another error then it will be our message' suffix. If so, cut it since otherwise we will
			// print it again as part of formatting that error.
			if inner != nil {
				if strings.HasSuffix(msg, inner.Error()) {
					msg = msg[:len(msg)-len(inner.Error())]
					// Cut last space if needed but only if there is stacktrace present (very likely)
					if e.stacktrace != nil && strings.HasSuffix(msg, ": ") {
						msg = msg[:len(msg)-1]
					}
				}
			}

			_, _ = io.WriteString(s, msg)
			if e.stacktrace != nil {
				// New line is useful only when printing frames, otherwise it is better to print next error in the chain
				// right after we print this one
				_, _ = io.WriteString(s, "\n")
				writeStackTrace(s, e.stacktrace)
			}

			// Print next error down the chain if there is one
			if inner != nil {
				_, _ = fmt.Fprintf(s, "%+v", inner)
			}

			return
		}
		fallthrough
	case 's':
		_, _ = io.WriteString(s, e.err.Error())
	case 'q':
		_, _ = fmt.Fprintf(s, "%q", e.err.Error())
	}
}

func (e *wrappedErrorf) Error() string {
	// Wrapped error has correct formatting
	return e.err.Error()
}

func (e *wrappedErrorf) Unwrap() error {
	// Skip wrapped error and return whatever it is wrapping if inner error contains single error
	// TODO: test for correct unwrap
	if _, ok := e.err.(interface{ Unwrap() []error }); ok {
		return e.err
	}

	return Unwrap(e.err)
}

func (e *wrappedErrorf) StackTrace() *xruntime.StackTrace {
	return e.stacktrace
}