File: merrors.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 (215 lines) | stat: -rw-r--r-- 6,043 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
// Copyright (c) The EfficientGo Authors.
// Licensed under the Apache License 2.0.

package merrors

import (
	"bytes"
	stderrors "errors"
	"fmt"
	"io"
)

// NilOrMultiError type allows combining multiple errors into one.
type NilOrMultiError struct {
	errs []error
}

// New returns NilOrMultiError with provided errors added if not nil.
func New(errs ...error) *NilOrMultiError {
	m := &NilOrMultiError{}
	m.Add(errs...)
	return m
}

// Add adds single or many errors to the error list. Each error is added only if not nil.
// If the error is a multiError type, the errors inside multiError are added to the main NilOrMultiError.
func (e *NilOrMultiError) Add(errs ...error) {
	for _, err := range errs {
		if err == nil {
			continue
		}
		if merr, ok := err.(multiError); ok {
			e.errs = append(e.errs, merr.errs...)
			continue
		}
		e.errs = append(e.errs, err)
	}
}

// Err returns the error list as an Error (also implements error) or nil if it is empty.
func (e NilOrMultiError) Err() Error {
	if len(e.errs) == 0 {
		return nil
	}
	return multiError(e)
}

// Error is extended error interface that allows to use returned read-only multi error in more advanced ways.
type Error interface {
	error

	// Errors returns underlying errors.
	Errors() []error

	// As finds the first error in multiError slice of error chains that matches target, and if so, sets
	// target to that error value and returns true. Otherwise, it returns false.
	//
	// 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.
	As(target interface{}) bool
	// Is returns true if any error in multiError's slice of error chains matches the given target or
	// if the target is of multiError type.
	//
	// An error is considered to match a target if it is equal to that target or if
	// it implements a method Is(error) bool such that Is(target) returns true.
	Is(target error) bool
	// Count returns the number of multi error' errors that match the given target.
	// Matching is defined as in Is method.
	Count(target error) int
}

// multiError implements the error and Error interfaces, and it represents NilOrMultiError (in other words []error) with at least one error inside it.
// NOTE: This type is useful to make sure that NilOrMultiError is not accidentally used for err != nil check.
type multiError struct {
	errs []error
}

// Errors returns underlying errors.
func (e multiError) Errors() []error {
	return e.errs
}

// Error returns a concatenated string of the contained errors.
func (e multiError) Error() string {
	var buf bytes.Buffer

	if len(e.errs) > 1 {
		fmt.Fprintf(&buf, "%d errors: ", len(e.errs))
	}
	for i, err := range e.errs {
		if i != 0 {
			buf.WriteString("; ")
		}
		buf.WriteString(err.Error())
	}

	return buf.String()
}

// As finds the first error in multiError slice of error chains that matches target, and if so, sets
// target to that error value and returns true. Otherwise, it returns false.
//
// 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.
func (e multiError) As(target interface{}) bool {
	if t, ok := target.(*multiError); ok {
		*t = e
		return true
	}

	for _, err := range e.errs {
		if stderrors.As(err, target) {
			return true
		}
	}
	return false
}

// Is returns true if any error in multiError's slice of error chains matches the given target or
// if the target is of multiError type.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
func (e multiError) Is(target error) bool {
	if m, ok := target.(multiError); ok {
		if len(m.errs) != len(e.errs) {
			return false
		}
		for i := 0; i < len(e.errs); i++ {
			if !stderrors.Is(m.errs[i], e.errs[i]) {
				return false
			}
		}
		return true
	}
	for _, err := range e.errs {
		if stderrors.Is(err, target) {
			return true
		}
	}
	return false
}

// Count returns the number of all multi error' errors that match the given target (including nested multi errors).
// Matching is defined as in Is method.
func (e multiError) Count(target error) (count int) {
	for _, err := range e.errs {
		if inner, ok := AsMulti(err); ok {
			count += inner.Count(target)
			continue
		}

		if stderrors.Is(err, target) {
			count++
		}
	}
	return count
}

// AsMulti casts error to multi error read only interface. It returns multi error and true if error matches multi error as
// defined by As method. If returns false if no multi error can be found.
func AsMulti(err error) (Error, bool) {
	m := multiError{}
	if !stderrors.As(err, &m) {
		return nil, false
	}
	return m, true
}

// Merge merges multiple Error to single one, but joining all errors together.
// NOTE: Nested multi errors are not merged.
func Merge(errs []Error) Error {
	e := multiError{}
	for _, err := range errs {
		e.errs = append(e.errs, err.Errors()...)
	}
	return e
}

// PrettyPrint prints the same information as multiError.Error() method but with newlines and indentation targeted
// for humans.
func PrettyPrint(w io.Writer, err Error) error {
	return prettyPrint(w, "\t", err)
}

func prettyPrint(w io.Writer, indent string, merr Error) error {
	if len(merr.Errors()) > 1 {
		if _, err := fmt.Fprintf(w, "%d errors:\n", len(merr.Errors())); err != nil {
			return err
		}
	}
	for i, err := range merr.Errors() {
		if i != 0 {
			if _, err := w.Write([]byte("\n")); err != nil {
				return err
			}
		}

		if merr2, ok := AsMulti(err); ok {
			if err := prettyPrint(w, indent+"\t", merr2); err != nil {
				return nil
			}
			continue
		}

		if _, err := w.Write([]byte(indent + err.Error())); err != nil {
			return err
		}
	}
	return nil
}