You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

179 lines
4.4 KiB

  1. // Package errwrap implements methods to formalize error wrapping in Go.
  2. //
  3. // All of the top-level functions that take an `error` are built to be able
  4. // to take any error, not just wrapped errors. This allows you to use errwrap
  5. // without having to type-check and type-cast everywhere.
  6. package errwrap
  7. import (
  8. "errors"
  9. "reflect"
  10. "strings"
  11. )
  12. // WalkFunc is the callback called for Walk.
  13. type WalkFunc func(error)
  14. // Wrapper is an interface that can be implemented by custom types to
  15. // have all the Contains, Get, etc. functions in errwrap work.
  16. //
  17. // When Walk reaches a Wrapper, it will call the callback for every
  18. // wrapped error in addition to the wrapper itself. Since all the top-level
  19. // functions in errwrap use Walk, this means that all those functions work
  20. // with your custom type.
  21. type Wrapper interface {
  22. WrappedErrors() []error
  23. }
  24. // Wrap defines that outer wraps inner, returning an error type that
  25. // can be cleanly used with the other methods in this package, such as
  26. // Contains, GetAll, etc.
  27. //
  28. // This function won't modify the error message at all (the outer message
  29. // will be used).
  30. func Wrap(outer, inner error) error {
  31. return &wrappedError{
  32. Outer: outer,
  33. Inner: inner,
  34. }
  35. }
  36. // Wrapf wraps an error with a formatting message. This is similar to using
  37. // `fmt.Errorf` to wrap an error. If you're using `fmt.Errorf` to wrap
  38. // errors, you should replace it with this.
  39. //
  40. // format is the format of the error message. The string '{{err}}' will
  41. // be replaced with the original error message.
  42. //
  43. // Deprecated: Use fmt.Errorf()
  44. func Wrapf(format string, err error) error {
  45. outerMsg := "<nil>"
  46. if err != nil {
  47. outerMsg = err.Error()
  48. }
  49. outer := errors.New(strings.Replace(
  50. format, "{{err}}", outerMsg, -1))
  51. return Wrap(outer, err)
  52. }
  53. // Contains checks if the given error contains an error with the
  54. // message msg. If err is not a wrapped error, this will always return
  55. // false unless the error itself happens to match this msg.
  56. func Contains(err error, msg string) bool {
  57. return len(GetAll(err, msg)) > 0
  58. }
  59. // ContainsType checks if the given error contains an error with
  60. // the same concrete type as v. If err is not a wrapped error, this will
  61. // check the err itself.
  62. func ContainsType(err error, v interface{}) bool {
  63. return len(GetAllType(err, v)) > 0
  64. }
  65. // Get is the same as GetAll but returns the deepest matching error.
  66. func Get(err error, msg string) error {
  67. es := GetAll(err, msg)
  68. if len(es) > 0 {
  69. return es[len(es)-1]
  70. }
  71. return nil
  72. }
  73. // GetType is the same as GetAllType but returns the deepest matching error.
  74. func GetType(err error, v interface{}) error {
  75. es := GetAllType(err, v)
  76. if len(es) > 0 {
  77. return es[len(es)-1]
  78. }
  79. return nil
  80. }
  81. // GetAll gets all the errors that might be wrapped in err with the
  82. // given message. The order of the errors is such that the outermost
  83. // matching error (the most recent wrap) is index zero, and so on.
  84. func GetAll(err error, msg string) []error {
  85. var result []error
  86. Walk(err, func(err error) {
  87. if err.Error() == msg {
  88. result = append(result, err)
  89. }
  90. })
  91. return result
  92. }
  93. // GetAllType gets all the errors that are the same type as v.
  94. //
  95. // The order of the return value is the same as described in GetAll.
  96. func GetAllType(err error, v interface{}) []error {
  97. var result []error
  98. var search string
  99. if v != nil {
  100. search = reflect.TypeOf(v).String()
  101. }
  102. Walk(err, func(err error) {
  103. var needle string
  104. if err != nil {
  105. needle = reflect.TypeOf(err).String()
  106. }
  107. if needle == search {
  108. result = append(result, err)
  109. }
  110. })
  111. return result
  112. }
  113. // Walk walks all the wrapped errors in err and calls the callback. If
  114. // err isn't a wrapped error, this will be called once for err. If err
  115. // is a wrapped error, the callback will be called for both the wrapper
  116. // that implements error as well as the wrapped error itself.
  117. func Walk(err error, cb WalkFunc) {
  118. if err == nil {
  119. return
  120. }
  121. switch e := err.(type) {
  122. case *wrappedError:
  123. cb(e.Outer)
  124. Walk(e.Inner, cb)
  125. case Wrapper:
  126. cb(err)
  127. for _, err := range e.WrappedErrors() {
  128. Walk(err, cb)
  129. }
  130. case interface{ Unwrap() error }:
  131. cb(err)
  132. Walk(e.Unwrap(), cb)
  133. default:
  134. cb(err)
  135. }
  136. }
  137. // wrappedError is an implementation of error that has both the
  138. // outer and inner errors.
  139. type wrappedError struct {
  140. Outer error
  141. Inner error
  142. }
  143. func (w *wrappedError) Error() string {
  144. return w.Outer.Error()
  145. }
  146. func (w *wrappedError) WrappedErrors() []error {
  147. return []error{w.Outer, w.Inner}
  148. }
  149. func (w *wrappedError) Unwrap() error {
  150. return w.Inner
  151. }