go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/errors/walk.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package errors
    16  
    17  // Walk performs a depth-first traversal of the supplied error, unfolding it and
    18  // invoke the supplied callback for each layered error recursively. If the
    19  // callback returns true, Walk will continue its traversal.
    20  //
    21  //   - If walk encounters a MultiError, the callback is called once for the
    22  //     outer MultiError, then once for each inner error.
    23  //   - If walk encounters a Wrapped error, the callback is called for the outer
    24  //     and inner error.
    25  //   - If an inner error is, itself, a container, Walk will recurse into it.
    26  //
    27  // If err is nil, the callback will not be invoked.
    28  func Walk(err error, fn func(error) bool) {
    29  	_ = walkVisit(err, fn, false)
    30  }
    31  
    32  // WalkLeaves is like Walk, but only calls fn on leaf nodes.
    33  func WalkLeaves(err error, fn func(error) bool) {
    34  	_ = walkVisit(err, fn, true)
    35  }
    36  
    37  func walkVisit(err error, fn func(error) bool, leavesOnly bool) bool {
    38  	if err == nil {
    39  		return true
    40  	}
    41  
    42  	// Call fn if we are not in leavesOnly mode.
    43  	if !(leavesOnly || fn(err)) {
    44  		return false
    45  	}
    46  
    47  	switch t := err.(type) {
    48  	case MultiError:
    49  		for _, e := range t {
    50  			if !walkVisit(e, fn, leavesOnly) {
    51  				return false
    52  			}
    53  		}
    54  
    55  	case Wrapped:
    56  		return walkVisit(t.Unwrap(), fn, leavesOnly)
    57  
    58  	default:
    59  		if leavesOnly {
    60  			return fn(err)
    61  		}
    62  	}
    63  
    64  	return true
    65  }
    66  
    67  // Any performs a Walk traversal of an error, returning true (and
    68  // short-circuiting) if the supplied filter function returns true for any
    69  // visited error.
    70  //
    71  // If err is nil, Any will return false.
    72  func Any(err error, fn func(error) bool) (any bool) {
    73  	Walk(err, func(err error) bool {
    74  		any = fn(err)
    75  		return !any
    76  	})
    77  	return
    78  }
    79  
    80  // Contains performs a Walk traversal of |outer|, returning true if any visited
    81  // error is equal to |inner|.
    82  func Contains(outer error, inner error) bool {
    83  	return Any(outer, func(item error) bool {
    84  		if item == inner {
    85  			return true
    86  		}
    87  		if is, ok := item.(interface{ Is(error) bool }); ok {
    88  			return is.Is(inner)
    89  		}
    90  		return false
    91  	})
    92  }