errors.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package errors
  14. import (
  15. "errors"
  16. "fmt"
  17. "k8s.io/apimachinery/pkg/util/sets"
  18. )
  19. // MessageCountMap contains occurrence for each error message.
  20. // Deprecated: Not used anymore in the k8s.io codebase, use `errors.Join` instead.
  21. type MessageCountMap map[string]int
  22. // Aggregate represents an object that contains multiple errors, but does not
  23. // necessarily have singular semantic meaning.
  24. // The aggregate can be used with `errors.Is()` to check for the occurrence of
  25. // a specific error type.
  26. // Errors.As() is not supported, because the caller presumably cares about a
  27. // specific error of potentially multiple that match the given type.
  28. type Aggregate interface {
  29. error
  30. Errors() []error
  31. Is(error) bool
  32. }
  33. // NewAggregate converts a slice of errors into an Aggregate interface, which
  34. // is itself an implementation of the error interface. If the slice is empty,
  35. // this returns nil.
  36. // It will check if any of the element of input error list is nil, to avoid
  37. // nil pointer panic when call Error().
  38. func NewAggregate(errlist []error) Aggregate {
  39. if len(errlist) == 0 {
  40. return nil
  41. }
  42. // In case of input error list contains nil
  43. var errs []error
  44. for _, e := range errlist {
  45. if e != nil {
  46. errs = append(errs, e)
  47. }
  48. }
  49. if len(errs) == 0 {
  50. return nil
  51. }
  52. return aggregate(errs)
  53. }
  54. // This helper implements the error and Errors interfaces. Keeping it private
  55. // prevents people from making an aggregate of 0 errors, which is not
  56. // an error, but does satisfy the error interface.
  57. type aggregate []error
  58. // Error is part of the error interface.
  59. func (agg aggregate) Error() string {
  60. if len(agg) == 0 {
  61. // This should never happen, really.
  62. return ""
  63. }
  64. if len(agg) == 1 {
  65. return agg[0].Error()
  66. }
  67. seenerrs := sets.NewString()
  68. result := ""
  69. agg.visit(func(err error) bool {
  70. msg := err.Error()
  71. if seenerrs.Has(msg) {
  72. return false
  73. }
  74. seenerrs.Insert(msg)
  75. if len(seenerrs) > 1 {
  76. result += ", "
  77. }
  78. result += msg
  79. return false
  80. })
  81. if len(seenerrs) == 1 {
  82. return result
  83. }
  84. return "[" + result + "]"
  85. }
  86. func (agg aggregate) Is(target error) bool {
  87. return agg.visit(func(err error) bool {
  88. return errors.Is(err, target)
  89. })
  90. }
  91. func (agg aggregate) visit(f func(err error) bool) bool {
  92. for _, err := range agg {
  93. switch err := err.(type) {
  94. case aggregate:
  95. if match := err.visit(f); match {
  96. return match
  97. }
  98. case Aggregate:
  99. for _, nestedErr := range err.Errors() {
  100. if match := f(nestedErr); match {
  101. return match
  102. }
  103. }
  104. default:
  105. if match := f(err); match {
  106. return match
  107. }
  108. }
  109. }
  110. return false
  111. }
  112. // Errors is part of the Aggregate interface.
  113. func (agg aggregate) Errors() []error {
  114. return []error(agg)
  115. }
  116. // Matcher is used to match errors. Returns true if the error matches.
  117. type Matcher func(error) bool
  118. // FilterOut removes all errors that match any of the matchers from the input
  119. // error. If the input is a singular error, only that error is tested. If the
  120. // input implements the Aggregate interface, the list of errors will be
  121. // processed recursively.
  122. //
  123. // This can be used, for example, to remove known-OK errors (such as io.EOF or
  124. // os.PathNotFound) from a list of errors.
  125. func FilterOut(err error, fns ...Matcher) error {
  126. if err == nil {
  127. return nil
  128. }
  129. if agg, ok := err.(Aggregate); ok {
  130. return NewAggregate(filterErrors(agg.Errors(), fns...))
  131. }
  132. if !matchesError(err, fns...) {
  133. return err
  134. }
  135. return nil
  136. }
  137. // matchesError returns true if any Matcher returns true
  138. func matchesError(err error, fns ...Matcher) bool {
  139. for _, fn := range fns {
  140. if fn(err) {
  141. return true
  142. }
  143. }
  144. return false
  145. }
  146. // filterErrors returns any errors (or nested errors, if the list contains
  147. // nested Errors) for which all fns return false. If no errors
  148. // remain a nil list is returned. The resulting slice will have all
  149. // nested slices flattened as a side effect.
  150. func filterErrors(list []error, fns ...Matcher) []error {
  151. result := []error{}
  152. for _, err := range list {
  153. r := FilterOut(err, fns...)
  154. if r != nil {
  155. result = append(result, r)
  156. }
  157. }
  158. return result
  159. }
  160. // Flatten takes an Aggregate, which may hold other Aggregates in arbitrary
  161. // nesting, and flattens them all into a single Aggregate, recursively.
  162. func Flatten(agg Aggregate) Aggregate {
  163. result := []error{}
  164. if agg == nil {
  165. return nil
  166. }
  167. for _, err := range agg.Errors() {
  168. if a, ok := err.(Aggregate); ok {
  169. r := Flatten(a)
  170. if r != nil {
  171. result = append(result, r.Errors()...)
  172. }
  173. } else {
  174. if err != nil {
  175. result = append(result, err)
  176. }
  177. }
  178. }
  179. return NewAggregate(result)
  180. }
  181. // CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate
  182. // Deprecated: Not used anymore in the k8s.io codebase, use `errors.Join` instead.
  183. func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate {
  184. if m == nil {
  185. return nil
  186. }
  187. result := make([]error, 0, len(m))
  188. for errStr, count := range m {
  189. var countStr string
  190. if count > 1 {
  191. countStr = fmt.Sprintf(" (repeated %v times)", count)
  192. }
  193. result = append(result, fmt.Errorf("%v%v", errStr, countStr))
  194. }
  195. return NewAggregate(result)
  196. }
  197. // Reduce will return err or nil, if err is an Aggregate and only has one item,
  198. // the first item in the aggregate.
  199. func Reduce(err error) error {
  200. if agg, ok := err.(Aggregate); ok && err != nil {
  201. switch len(agg.Errors()) {
  202. case 1:
  203. return agg.Errors()[0]
  204. case 0:
  205. return nil
  206. }
  207. }
  208. return err
  209. }
  210. // AggregateGoroutines runs the provided functions in parallel, stuffing all
  211. // non-nil errors into the returned Aggregate.
  212. // Returns nil if all the functions complete successfully.
  213. func AggregateGoroutines(funcs ...func() error) Aggregate {
  214. errChan := make(chan error, len(funcs))
  215. for _, f := range funcs {
  216. go func(f func() error) { errChan <- f() }(f)
  217. }
  218. errs := make([]error, 0)
  219. for i := 0; i < cap(errChan); i++ {
  220. if err := <-errChan; err != nil {
  221. errs = append(errs, err)
  222. }
  223. }
  224. return NewAggregate(errs)
  225. }
  226. // ErrPreconditionViolated is returned when the precondition is violated
  227. var ErrPreconditionViolated = errors.New("precondition is violated")