From 7e03ec76abcf62026438817ba2c8347423540ca2 Mon Sep 17 00:00:00 2001 From: Benjamin Elder Date: Tue, 16 Nov 2021 16:10:54 -0800 Subject: [PATCH] fork k8s.io/apimachinery/pkg/util/errors.Aggregate --- pkg/errors/aggregate.go | 16 ++-- pkg/errors/aggregate_forked.go | 167 +++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 pkg/errors/aggregate_forked.go diff --git a/pkg/errors/aggregate.go b/pkg/errors/aggregate.go index d8ed79f0..5258cd2e 100644 --- a/pkg/errors/aggregate.go +++ b/pkg/errors/aggregate.go @@ -16,18 +16,14 @@ limitations under the License. package errors -import ( - k8serrors "k8s.io/apimachinery/pkg/util/errors" -) - -// NewAggregate is a k8s.io/apimachinery/pkg/util/errors.NewAggregate wrapper +// NewAggregate is a k8s.io/apimachinery/pkg/util/errors.NewAggregate compatible wrapper // note that while it returns a StackTrace wrapped Aggregate // That has been Flattened and Reduced func NewAggregate(errlist []error) error { return WithStack( - k8serrors.Reduce( - k8serrors.Flatten( - k8serrors.NewAggregate(errlist), + reduce( + flatten( + newAggregate(errlist), ), ), ) @@ -35,9 +31,9 @@ func NewAggregate(errlist []error) error { // Errors returns the deepest Aggregate in a Cause chain func Errors(err error) []error { - var errors k8serrors.Aggregate + var errors Aggregate for { - if v, ok := err.(k8serrors.Aggregate); ok { + if v, ok := err.(Aggregate); ok { errors = v } if causerErr, ok := err.(Causer); ok { diff --git a/pkg/errors/aggregate_forked.go b/pkg/errors/aggregate_forked.go new file mode 100644 index 00000000..3e9ec30b --- /dev/null +++ b/pkg/errors/aggregate_forked.go @@ -0,0 +1,167 @@ +/* +Copyright 2021 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" + + "sigs.k8s.io/kind/pkg/internal/sets" +) + +/* + The contents of this file are lightly forked from k8s.io/apimachinery/pkg/util/errors + Forking makes kind easier to import, and this code is stable. + + Currently the only source changes are renaming some methods so as to not + export them. +*/ + +// Aggregate represents an object that contains multiple errors, but does not +// necessarily have singular semantic meaning. +// The aggregate can be used with `errors.Is()` to check for the occurrence of +// a specific error type. +// Errors.As() is not supported, because the caller presumably cares about a +// specific error of potentially multiple that match the given type. +// +// NOTE: this type is originally from k8s.io/apimachinery/pkg/util/errors.Aggregate +// Since it is an interface, you can use the implementing types interchangeably +type Aggregate interface { + error + Errors() []error + Is(error) bool +} + +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) +} + +// 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) +} + +// 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 +} + +// 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() + } + seenerrs := sets.NewString() + result := "" + agg.visit(func(err error) bool { + msg := err.Error() + if seenerrs.Has(msg) { + return false + } + seenerrs.Insert(msg) + if len(seenerrs) > 1 { + result += ", " + } + result += msg + return false + }) + if len(seenerrs) == 1 { + return result + } + return "[" + result + "]" +} + +func (agg aggregate) Is(target error) bool { + return agg.visit(func(err error) bool { + return errors.Is(err, target) + }) +} + +func (agg aggregate) visit(f func(err error) bool) bool { + for _, err := range agg { + switch err := err.(type) { + case aggregate: + if match := err.visit(f); match { + return match + } + case Aggregate: + for _, nestedErr := range err.Errors() { + if match := f(nestedErr); match { + return match + } + } + default: + if match := f(err); match { + return match + } + } + } + + return false +} + +// Errors is part of the Aggregate interface. +func (agg aggregate) Errors() []error { + return []error(agg) +}