aggregate.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Copyright 2019 Yunion
  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. package errors
  15. import (
  16. "fmt"
  17. )
  18. // MessageCountMap contains occurance for each error message.
  19. type MessageCountMap map[string]int
  20. // Aggregate represents an object that contains multiple errors, but does not
  21. // necessarily have singular semantic meaning.
  22. type Aggregate interface {
  23. error
  24. Errors() []error
  25. }
  26. // NewAggregate converts a slice of errors into an Aggregate interface, which
  27. // is itself an implementation of the error interface. If the slice is empty,
  28. // this returns nil.
  29. // It will check if any of the element of input error list is nil, to avoid
  30. // nil pointer panic when call Error().
  31. func NewAggregate(errlist []error) Aggregate {
  32. if len(errlist) == 0 {
  33. return nil
  34. }
  35. // In case of input error list contains nil
  36. var errs []error
  37. for _, e := range errlist {
  38. if e != nil {
  39. errs = append(errs, e)
  40. }
  41. }
  42. if len(errs) == 0 {
  43. return nil
  44. }
  45. return aggregate(errs)
  46. }
  47. // This helper implements the error and Errors interfaces. Keeping it private
  48. // prevents people from making an aggregate of 0 errors, which is not
  49. // an error, but does satisfy the error interface.
  50. type aggregate []error
  51. // Error is part of the error interface.
  52. func (agg aggregate) Error() string {
  53. if len(agg) == 0 {
  54. // This should never happen, really.
  55. return ""
  56. }
  57. if len(agg) == 1 {
  58. return agg[0].Error()
  59. }
  60. result := fmt.Sprintf("[%s", agg[0].Error())
  61. for i := 1; i < len(agg); i++ {
  62. result += fmt.Sprintf(", %s", agg[i].Error())
  63. }
  64. result += "]"
  65. return result
  66. }
  67. // Errors is part of the Aggregate interface.
  68. func (agg aggregate) Errors() []error {
  69. return []error(agg)
  70. }
  71. func (agg aggregate) Cause() error {
  72. if len(agg) == 0 {
  73. return nil
  74. }
  75. if len(agg) == 1 {
  76. return Cause(agg[0])
  77. }
  78. return ErrAggregate
  79. }
  80. // Matcher is used to match errors. Returns true if the error matches.
  81. type Matcher func(error) bool
  82. // FilterOut removes all errors that match any of the matchers from the input
  83. // error. If the input is a singular error, only that error is tested. If the
  84. // input implements the Aggregate interface, the list of errors will be
  85. // processed recursively.
  86. //
  87. // This can be used, for example, to remove known-OK errors (such as io.EOF or
  88. // os.PathNotFound) from a list of errors.
  89. func FilterOut(err error, fns ...Matcher) error {
  90. if err == nil {
  91. return nil
  92. }
  93. if agg, ok := err.(Aggregate); ok {
  94. return NewAggregate(filterErrors(agg.Errors(), fns...))
  95. }
  96. if !matchesError(err, fns...) {
  97. return err
  98. }
  99. return nil
  100. }
  101. // matchesError returns true if any Matcher returns true
  102. func matchesError(err error, fns ...Matcher) bool {
  103. for _, fn := range fns {
  104. if fn(err) {
  105. return true
  106. }
  107. }
  108. return false
  109. }
  110. // filterErrors returns any errors (or nested errors, if the list contains
  111. // nested Errors) for which all fns return false. If no errors
  112. // remain a nil list is returned. The resulting silec will have all
  113. // nested slices flattened as a side effect.
  114. func filterErrors(list []error, fns ...Matcher) []error {
  115. result := []error{}
  116. for _, err := range list {
  117. r := FilterOut(err, fns...)
  118. if r != nil {
  119. result = append(result, r)
  120. }
  121. }
  122. return result
  123. }
  124. // Flatten takes an Aggregate, which may hold other Aggregates in arbitrary
  125. // nesting, and flattens them all into a single Aggregate, recursively.
  126. func Flatten(agg Aggregate) Aggregate {
  127. result := []error{}
  128. if agg == nil {
  129. return nil
  130. }
  131. for _, err := range agg.Errors() {
  132. if a, ok := err.(Aggregate); ok {
  133. r := Flatten(a)
  134. if r != nil {
  135. result = append(result, r.Errors()...)
  136. }
  137. } else {
  138. if err != nil {
  139. result = append(result, err)
  140. }
  141. }
  142. }
  143. return NewAggregate(result)
  144. }
  145. // CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate
  146. func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate {
  147. if m == nil {
  148. return nil
  149. }
  150. result := make([]error, 0, len(m))
  151. for errStr, count := range m {
  152. var countStr string
  153. if count > 1 {
  154. countStr = fmt.Sprintf(" (repeated %v times)", count)
  155. }
  156. result = append(result, fmt.Errorf("%v%v", errStr, countStr))
  157. }
  158. return NewAggregate(result)
  159. }
  160. // Reduce will return err or, if err is an Aggregate and only has one item,
  161. // the first item in the aggregate.
  162. func Reduce(err error) error {
  163. if agg, ok := err.(Aggregate); ok && err != nil {
  164. switch len(agg.Errors()) {
  165. case 1:
  166. return agg.Errors()[0]
  167. case 0:
  168. return nil
  169. }
  170. }
  171. return err
  172. }
  173. // AggregateGoroutines runs the provided functions in parallel, stuffing all
  174. // non-nil errors into the returned Aggregate.
  175. // Returns nil if all the functions complete successfully.
  176. func AggregateGoroutines(funcs ...func() error) Aggregate {
  177. errChan := make(chan error, len(funcs))
  178. for _, f := range funcs {
  179. go func(f func() error) { errChan <- f() }(f)
  180. }
  181. errs := make([]error, 0)
  182. for i := 0; i < cap(errChan); i++ {
  183. if err := <-errChan; err != nil {
  184. errs = append(errs, err)
  185. }
  186. }
  187. return NewAggregate(errs)
  188. }
  189. // ErrPreconditionViolated is returned when the precondition is violated
  190. // var ErrPreconditionViolated = errors.New("precondition is violated")