aboutsummaryrefslogtreecommitdiffstats
path: root/library/go/core/xerrors/sentinel.go
blob: 6651588619d8cf8948cc41d231bae783c7241044 (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
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
package xerrors

import (
	"errors"
	"fmt"
	"io"
	"strings"

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

// NewSentinel acts as New but does not add stack frame
func NewSentinel(text string) *Sentinel {
	return &Sentinel{error: errors.New(text)}
}

// Sentinel error
type Sentinel struct {
	error
}

// WithFrame adds stack frame to sentinel error (DEPRECATED)
func (s *Sentinel) WithFrame() error {
	return &sentinelWithStackTrace{
		err:        s,
		stacktrace: newStackTrace(1, nil),
	}
}

func (s *Sentinel) WithStackTrace() error {
	return &sentinelWithStackTrace{
		err:        s,
		stacktrace: newStackTrace(1, nil),
	}
}

// Wrap error with this sentinel error. Adds stack frame.
func (s *Sentinel) Wrap(err error) error {
	if err == nil {
		panic("tried to wrap a nil error")
	}

	return &sentinelWrapper{
		err:        s,
		wrapped:    err,
		stacktrace: newStackTrace(1, err),
	}
}

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

func (e *sentinelWithStackTrace) Error() string {
	return e.err.Error()
}

func (e *sentinelWithStackTrace) Format(s fmt.State, v rune) {
	switch v {
	case 'v':
		if s.Flag('+') && e.stacktrace != nil {
			msg := e.err.Error()
			_, _ = io.WriteString(s, msg)
			writeMsgAndStackTraceSeparator(s, msg)
			writeStackTrace(s, e.stacktrace)
			return
		}
		fallthrough
	case 's':
		_, _ = io.WriteString(s, e.err.Error())
	case 'q':
		_, _ = fmt.Fprintf(s, "%q", e.err.Error())
	}
}

func writeMsgAndStackTraceSeparator(w io.Writer, msg string) {
	separator := "\n"
	if !strings.HasSuffix(msg, ":") {
		separator = ":\n"
	}

	_, _ = io.WriteString(w, separator)
}

// Is checks if e holds the specified error. Checks only immediate error.
func (e *sentinelWithStackTrace) Is(target error) bool {
	return e.err == target
}

// As checks if ew holds the specified error type. Checks only immediate error.
// It does NOT perform target checks as it relies on errors.As to do it
func (e *sentinelWithStackTrace) As(target interface{}) bool {
	return xreflect.Assign(e.err, target)
}

type sentinelWrapper struct {
	err        error
	wrapped    error
	stacktrace *xruntime.StackTrace
}

func (e *sentinelWrapper) Error() string {
	return fmt.Sprintf("%s", e)
}

func (e *sentinelWrapper) Format(s fmt.State, v rune) {
	switch v {
	case 'v':
		if s.Flag('+') {
			if e.stacktrace != nil {
				msg := e.err.Error()
				_, _ = io.WriteString(s, msg)
				writeMsgAndStackTraceSeparator(s, msg)
				writeStackTrace(s, e.stacktrace)
				_, _ = fmt.Fprintf(s, "%+v", e.wrapped)
			} else {
				_, _ = io.WriteString(s, e.err.Error())
				_, _ = io.WriteString(s, ": ")
				_, _ = fmt.Fprintf(s, "%+v", e.wrapped)
			}

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

// Unwrap implements Wrapper interface
func (e *sentinelWrapper) Unwrap() error {
	return e.wrapped
}

// Is checks if ew holds the specified error. Checks only immediate error.
func (e *sentinelWrapper) Is(target error) bool {
	return e.err == target
}

// As checks if error holds the specified error type. Checks only immediate error.
// It does NOT perform target checks as it relies on errors.As to do it
func (e *sentinelWrapper) As(target interface{}) bool {
	return xreflect.Assign(e.err, target)
}