warnings.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Package warnings implements error handling with non-fatal errors (warnings).
  2. //
  3. // A recurring pattern in Go programming is the following:
  4. //
  5. // func myfunc(params) error {
  6. // if err := doSomething(...); err != nil {
  7. // return err
  8. // }
  9. // if err := doSomethingElse(...); err != nil {
  10. // return err
  11. // }
  12. // if ok := doAnotherThing(...); !ok {
  13. // return errors.New("my error")
  14. // }
  15. // ...
  16. // return nil
  17. // }
  18. //
  19. // This pattern allows interrupting the flow on any received error. But what if
  20. // there are errors that should be noted but still not fatal, for which the flow
  21. // should not be interrupted? Implementing such logic at each if statement would
  22. // make the code complex and the flow much harder to follow.
  23. //
  24. // Package warnings provides the Collector type and a clean and simple pattern
  25. // for achieving such logic. The Collector takes care of deciding when to break
  26. // the flow and when to continue, collecting any non-fatal errors (warnings)
  27. // along the way. The only requirement is that fatal and non-fatal errors can be
  28. // distinguished programmatically; that is a function such as
  29. //
  30. // IsFatal(error) bool
  31. //
  32. // must be implemented. The following is an example of what the above snippet
  33. // could look like using the warnings package:
  34. //
  35. // import "gopkg.in/warnings.v0"
  36. //
  37. // func isFatal(err error) bool {
  38. // _, ok := err.(WarningType)
  39. // return !ok
  40. // }
  41. //
  42. // func myfunc(params) error {
  43. // c := warnings.NewCollector(isFatal)
  44. // c.FatalWithWarnings = true
  45. // if err := c.Collect(doSomething()); err != nil {
  46. // return err
  47. // }
  48. // if err := c.Collect(doSomethingElse(...)); err != nil {
  49. // return err
  50. // }
  51. // if ok := doAnotherThing(...); !ok {
  52. // if err := c.Collect(errors.New("my error")); err != nil {
  53. // return err
  54. // }
  55. // }
  56. // ...
  57. // return c.Done()
  58. // }
  59. //
  60. // Rules for using warnings
  61. //
  62. // - ensure that warnings are programmatically distinguishable from fatal
  63. // errors (i.e. implement an isFatal function and any necessary error types)
  64. // - ensure that there is a single Collector instance for a call of each
  65. // exported function
  66. // - ensure that all errors (fatal or warning) are fed through Collect
  67. // - ensure that every time an error is returned, it is one returned by a
  68. // Collector (from Collect or Done)
  69. // - ensure that Collect is never called after Done
  70. //
  71. // TODO
  72. //
  73. // - optionally limit the number of warnings (e.g. stop after 20 warnings) (?)
  74. // - consider interaction with contexts
  75. // - go vet-style invocations verifier
  76. // - semi-automatic code converter
  77. //
  78. package warnings // import "gopkg.in/warnings.v0"
  79. import (
  80. "bytes"
  81. "fmt"
  82. )
  83. // List holds a collection of warnings and optionally one fatal error.
  84. type List struct {
  85. Warnings []error
  86. Fatal error
  87. }
  88. // Error implements the error interface.
  89. func (l List) Error() string {
  90. b := bytes.NewBuffer(nil)
  91. if l.Fatal != nil {
  92. fmt.Fprintln(b, "fatal:")
  93. fmt.Fprintln(b, l.Fatal)
  94. }
  95. switch len(l.Warnings) {
  96. case 0:
  97. // nop
  98. case 1:
  99. fmt.Fprintln(b, "warning:")
  100. default:
  101. fmt.Fprintln(b, "warnings:")
  102. }
  103. for _, err := range l.Warnings {
  104. fmt.Fprintln(b, err)
  105. }
  106. return b.String()
  107. }
  108. // A Collector collects errors up to the first fatal error.
  109. type Collector struct {
  110. // IsFatal distinguishes between warnings and fatal errors.
  111. IsFatal func(error) bool
  112. // FatalWithWarnings set to true means that a fatal error is returned as
  113. // a List together with all warnings so far. The default behavior is to
  114. // only return the fatal error and discard any warnings that have been
  115. // collected.
  116. FatalWithWarnings bool
  117. l List
  118. done bool
  119. }
  120. // NewCollector returns a new Collector; it uses isFatal to distinguish between
  121. // warnings and fatal errors.
  122. func NewCollector(isFatal func(error) bool) *Collector {
  123. return &Collector{IsFatal: isFatal}
  124. }
  125. // Collect collects a single error (warning or fatal). It returns nil if
  126. // collection can continue (only warnings so far), or otherwise the errors
  127. // collected. Collect mustn't be called after the first fatal error or after
  128. // Done has been called.
  129. func (c *Collector) Collect(err error) error {
  130. if c.done {
  131. panic("warnings.Collector already done")
  132. }
  133. if err == nil {
  134. return nil
  135. }
  136. if c.IsFatal(err) {
  137. c.done = true
  138. c.l.Fatal = err
  139. } else {
  140. c.l.Warnings = append(c.l.Warnings, err)
  141. }
  142. if c.l.Fatal != nil {
  143. return c.erorr()
  144. }
  145. return nil
  146. }
  147. // Done ends collection and returns the collected error(s).
  148. func (c *Collector) Done() error {
  149. c.done = true
  150. return c.erorr()
  151. }
  152. func (c *Collector) erorr() error {
  153. if !c.FatalWithWarnings && c.l.Fatal != nil {
  154. return c.l.Fatal
  155. }
  156. if c.l.Fatal == nil && len(c.l.Warnings) == 0 {
  157. return nil
  158. }
  159. // Note that a single warning is also returned as a List. This is to make it
  160. // easier to determine fatal-ness of the returned error.
  161. return c.l
  162. }
  163. // FatalOnly returns the fatal error, if any, **in an error returned by a
  164. // Collector**. It returns nil if and only if err is nil or err is a List
  165. // with err.Fatal == nil.
  166. func FatalOnly(err error) error {
  167. l, ok := err.(List)
  168. if !ok {
  169. return err
  170. }
  171. return l.Fatal
  172. }
  173. // WarningsOnly returns the warnings **in an error returned by a Collector**.
  174. func WarningsOnly(err error) []error {
  175. l, ok := err.(List)
  176. if !ok {
  177. return nil
  178. }
  179. return l.Warnings
  180. }