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
}
|