summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-errors/v2/standard.go
blob: 1d2c71c5feb9dc4d244fd66dc861dab01c3022c4 (plain)
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
package errors

import (
	"errors"
	"reflect"
	_ "unsafe"

	"codeberg.org/gruf/go-bitutil"
)

// errtype is a ptr to the error interface type.
var errtype = reflect.TypeOf((*error)(nil)).Elem()

// Comparable is functionally equivalent to calling errors.Is() on multiple errors (up to a max of 64).
func Comparable(err error, targets ...error) bool {
	var flags bitutil.Flags64

	// Flags only has 64 bit-slots
	if len(targets) > 64 {
		panic("too many targets")
	}

	for i := 0; i < len(targets); {
		if targets[i] == nil {
			if err == nil {
				return true
			}

			// Drop nil targets from slice.
			copy(targets[i:], targets[i+1:])
			targets = targets[:len(targets)-1]
			continue
		}

		// Check if this error is directly comparable
		if reflect.TypeOf(targets[i]).Comparable() {
			flags = flags.Set(uint8(i))
		}

		i++
	}

	for err != nil {
		// Check if this layer supports .Is interface
		is, ok := err.(interface{ Is(error) bool })

		if !ok {
			// Error does not support interface
			//
			// Only try perform direct compare
			for i := 0; i < len(targets); i++ {
				// Try directly compare errors
				if flags.Get(uint8(i)) &&
					err == targets[i] {
					return true
				}
			}
		} else {
			// Error supports the .Is interface
			//
			// Perform direct compare AND .Is()
			for i := 0; i < len(targets); i++ {
				if (flags.Get(uint8(i)) &&
					err == targets[i]) ||
					is.Is(targets[i]) {
					return true
				}
			}
		}

		// Unwrap to next layer
		err = errors.Unwrap(err)
	}

	return false
}

// Assignable is functionally equivalent to calling errors.As() on multiple errors,
// except that it only checks assignability as opposed to setting the target.
func Assignable(err error, targets ...error) bool {
	if err == nil {
		// Fastest case.
		return false
	}

	for i := 0; i < len(targets); {
		if targets[i] == nil {
			// Drop nil targets from slice.
			copy(targets[i:], targets[i+1:])
			targets = targets[:len(targets)-1]
			continue
		}
		i++
	}

	for err != nil {
		// Check if this layer supports .As interface
		as, ok := err.(interface{ As(any) bool })

		// Get reflected err type.
		te := reflect.TypeOf(err)

		if !ok {
			// Error does not support interface.
			//
			// Check assignability using reflection.
			for i := 0; i < len(targets); i++ {
				tt := reflect.TypeOf(targets[i])
				if te.AssignableTo(tt) {
					return true
				}
			}
		} else {
			// Error supports the .As interface.
			//
			// Check using .As() and reflection.
			for i := 0; i < len(targets); i++ {
				if as.As(targets[i]) {
					return true
				} else if tt := reflect.TypeOf(targets[i]); // nocollapse
				te.AssignableTo(tt) {
					return true
				}
			}
		}

		// Unwrap to next layer.
		err = errors.Unwrap(err)
	}

	return false
}

// As finds the first error in err's tree that matches target, and if one is found, sets
// target to that error value and returns true. Otherwise, it returns false.
//
// The tree consists of err itself, followed by the errors obtained by repeatedly
// calling Unwrap. When err wraps multiple errors, As examines err followed by a
// depth-first traversal of its children.
//
// An error matches target if the error's concrete value is assignable to the value
// pointed to by target, or if the error has a method As(interface{}) bool such that
// As(target) returns true. In the latter case, the As method is responsible for
// setting target.
//
// An error type might provide an As method so it can be treated as if it were a
// different error type.
//
// As panics if target is not a non-nil pointer to either a type that implements
// error, or to any interface type.
//
//go:linkname As errors.As
func As(err error, target any) bool

// 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.
//
//go:linkname Unwrap errors.Unwrap
func Unwrap(err error) error