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 }