// Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors import ( "fmt" ) // MessageCountMap contains occurance for each error message. type MessageCountMap map[string]int // Aggregate represents an object that contains multiple errors, but does not // necessarily have singular semantic meaning. type Aggregate interface { error Errors() []error } // NewAggregate converts a slice of errors into an Aggregate interface, which // is itself an implementation of the error interface. If the slice is empty, // this returns nil. // It will check if any of the element of input error list is nil, to avoid // nil pointer panic when call Error(). func NewAggregate(errlist []error) Aggregate { if len(errlist) == 0 { return nil } // In case of input error list contains nil var errs []error for _, e := range errlist { if e != nil { errs = append(errs, e) } } if len(errs) == 0 { return nil } return aggregate(errs) } // This helper implements the error and Errors interfaces. Keeping it private // prevents people from making an aggregate of 0 errors, which is not // an error, but does satisfy the error interface. type aggregate []error // Error is part of the error interface. func (agg aggregate) Error() string { if len(agg) == 0 { // This should never happen, really. return "" } if len(agg) == 1 { return agg[0].Error() } result := fmt.Sprintf("[%s", agg[0].Error()) for i := 1; i < len(agg); i++ { result += fmt.Sprintf(", %s", agg[i].Error()) } result += "]" return result } // Errors is part of the Aggregate interface. func (agg aggregate) Errors() []error { return []error(agg) } func (agg aggregate) Cause() error { if len(agg) == 0 { return nil } if len(agg) == 1 { return Cause(agg[0]) } return ErrAggregate } // Matcher is used to match errors. Returns true if the error matches. type Matcher func(error) bool // FilterOut removes all errors that match any of the matchers from the input // error. If the input is a singular error, only that error is tested. If the // input implements the Aggregate interface, the list of errors will be // processed recursively. // // This can be used, for example, to remove known-OK errors (such as io.EOF or // os.PathNotFound) from a list of errors. func FilterOut(err error, fns ...Matcher) error { if err == nil { return nil } if agg, ok := err.(Aggregate); ok { return NewAggregate(filterErrors(agg.Errors(), fns...)) } if !matchesError(err, fns...) { return err } return nil } // matchesError returns true if any Matcher returns true func matchesError(err error, fns ...Matcher) bool { for _, fn := range fns { if fn(err) { return true } } return false } // filterErrors returns any errors (or nested errors, if the list contains // nested Errors) for which all fns return false. If no errors // remain a nil list is returned. The resulting silec will have all // nested slices flattened as a side effect. func filterErrors(list []error, fns ...Matcher) []error { result := []error{} for _, err := range list { r := FilterOut(err, fns...) if r != nil { result = append(result, r) } } return result } // Flatten takes an Aggregate, which may hold other Aggregates in arbitrary // nesting, and flattens them all into a single Aggregate, recursively. func Flatten(agg Aggregate) Aggregate { result := []error{} if agg == nil { return nil } for _, err := range agg.Errors() { if a, ok := err.(Aggregate); ok { r := Flatten(a) if r != nil { result = append(result, r.Errors()...) } } else { if err != nil { result = append(result, err) } } } return NewAggregate(result) } // CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate { if m == nil { return nil } result := make([]error, 0, len(m)) for errStr, count := range m { var countStr string if count > 1 { countStr = fmt.Sprintf(" (repeated %v times)", count) } result = append(result, fmt.Errorf("%v%v", errStr, countStr)) } return NewAggregate(result) } // Reduce will return err or, if err is an Aggregate and only has one item, // the first item in the aggregate. func Reduce(err error) error { if agg, ok := err.(Aggregate); ok && err != nil { switch len(agg.Errors()) { case 1: return agg.Errors()[0] case 0: return nil } } return err } // AggregateGoroutines runs the provided functions in parallel, stuffing all // non-nil errors into the returned Aggregate. // Returns nil if all the functions complete successfully. func AggregateGoroutines(funcs ...func() error) Aggregate { errChan := make(chan error, len(funcs)) for _, f := range funcs { go func(f func() error) { errChan <- f() }(f) } errs := make([]error, 0) for i := 0; i < cap(errChan); i++ { if err := <-errChan; err != nil { errs = append(errs, err) } } return NewAggregate(errs) } // ErrPreconditionViolated is returned when the precondition is violated // var ErrPreconditionViolated = errors.New("precondition is violated")