fork k8s.io/apimachinery/pkg/util/errors.Aggregate

This commit is contained in:
Benjamin Elder
2021-11-16 16:10:54 -08:00
parent 63605424ce
commit 7e03ec76ab
2 changed files with 173 additions and 10 deletions

View File

@@ -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 {

View File

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