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.

warnings.go 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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. // For an example of a non-trivial code base using this library, see
  61. // gopkg.in/gcfg.v1
  62. //
  63. // Rules for using warnings
  64. //
  65. // - ensure that warnings are programmatically distinguishable from fatal
  66. // errors (i.e. implement an isFatal function and any necessary error types)
  67. // - ensure that there is a single Collector instance for a call of each
  68. // exported function
  69. // - ensure that all errors (fatal or warning) are fed through Collect
  70. // - ensure that every time an error is returned, it is one returned by a
  71. // Collector (from Collect or Done)
  72. // - ensure that Collect is never called after Done
  73. //
  74. // TODO
  75. //
  76. // - optionally limit the number of warnings (e.g. stop after 20 warnings) (?)
  77. // - consider interaction with contexts
  78. // - go vet-style invocations verifier
  79. // - semi-automatic code converter
  80. //
  81. package warnings // import "gopkg.in/warnings.v0"
  82. import (
  83. "bytes"
  84. "fmt"
  85. )
  86. // List holds a collection of warnings and optionally one fatal error.
  87. type List struct {
  88. Warnings []error
  89. Fatal error
  90. }
  91. // Error implements the error interface.
  92. func (l List) Error() string {
  93. b := bytes.NewBuffer(nil)
  94. if l.Fatal != nil {
  95. fmt.Fprintln(b, "fatal:")
  96. fmt.Fprintln(b, l.Fatal)
  97. }
  98. switch len(l.Warnings) {
  99. case 0:
  100. // nop
  101. case 1:
  102. fmt.Fprintln(b, "warning:")
  103. default:
  104. fmt.Fprintln(b, "warnings:")
  105. }
  106. for _, err := range l.Warnings {
  107. fmt.Fprintln(b, err)
  108. }
  109. return b.String()
  110. }
  111. // A Collector collects errors up to the first fatal error.
  112. type Collector struct {
  113. // IsFatal distinguishes between warnings and fatal errors.
  114. IsFatal func(error) bool
  115. // FatalWithWarnings set to true means that a fatal error is returned as
  116. // a List together with all warnings so far. The default behavior is to
  117. // only return the fatal error and discard any warnings that have been
  118. // collected.
  119. FatalWithWarnings bool
  120. l List
  121. done bool
  122. }
  123. // NewCollector returns a new Collector; it uses isFatal to distinguish between
  124. // warnings and fatal errors.
  125. func NewCollector(isFatal func(error) bool) *Collector {
  126. return &Collector{IsFatal: isFatal}
  127. }
  128. // Collect collects a single error (warning or fatal). It returns nil if
  129. // collection can continue (only warnings so far), or otherwise the errors
  130. // collected. Collect mustn't be called after the first fatal error or after
  131. // Done has been called.
  132. func (c *Collector) Collect(err error) error {
  133. if c.done {
  134. panic("warnings.Collector already done")
  135. }
  136. if err == nil {
  137. return nil
  138. }
  139. if c.IsFatal(err) {
  140. c.done = true
  141. c.l.Fatal = err
  142. } else {
  143. c.l.Warnings = append(c.l.Warnings, err)
  144. }
  145. if c.l.Fatal != nil {
  146. return c.erorr()
  147. }
  148. return nil
  149. }
  150. // Done ends collection and returns the collected error(s).
  151. func (c *Collector) Done() error {
  152. c.done = true
  153. return c.erorr()
  154. }
  155. func (c *Collector) erorr() error {
  156. if !c.FatalWithWarnings && c.l.Fatal != nil {
  157. return c.l.Fatal
  158. }
  159. if c.l.Fatal == nil && len(c.l.Warnings) == 0 {
  160. return nil
  161. }
  162. // Note that a single warning is also returned as a List. This is to make it
  163. // easier to determine fatal-ness of the returned error.
  164. return c.l
  165. }
  166. // FatalOnly returns the fatal error, if any, **in an error returned by a
  167. // Collector**. It returns nil if and only if err is nil or err is a List
  168. // with err.Fatal == nil.
  169. func FatalOnly(err error) error {
  170. l, ok := err.(List)
  171. if !ok {
  172. return err
  173. }
  174. return l.Fatal
  175. }
  176. // WarningsOnly returns the warnings **in an error returned by a Collector**.
  177. func WarningsOnly(err error) []error {
  178. l, ok := err.(List)
  179. if !ok {
  180. return nil
  181. }
  182. return l.Warnings
  183. }