| /* |
| Copyright 2015 The Kubernetes Authors. |
| |
| 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 ( |
| "errors" |
| "fmt" |
| ) |
| |
| // MessageCountMap contains occurrence 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) |
| } |
| |
| // 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") |