Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
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.
194 lines
5.4 KiB
194 lines
5.4 KiB
// Package warnings implements error handling with non-fatal errors (warnings). |
|
// |
|
// A recurring pattern in Go programming is the following: |
|
// |
|
// func myfunc(params) error { |
|
// if err := doSomething(...); err != nil { |
|
// return err |
|
// } |
|
// if err := doSomethingElse(...); err != nil { |
|
// return err |
|
// } |
|
// if ok := doAnotherThing(...); !ok { |
|
// return errors.New("my error") |
|
// } |
|
// ... |
|
// return nil |
|
// } |
|
// |
|
// This pattern allows interrupting the flow on any received error. But what if |
|
// there are errors that should be noted but still not fatal, for which the flow |
|
// should not be interrupted? Implementing such logic at each if statement would |
|
// make the code complex and the flow much harder to follow. |
|
// |
|
// Package warnings provides the Collector type and a clean and simple pattern |
|
// for achieving such logic. The Collector takes care of deciding when to break |
|
// the flow and when to continue, collecting any non-fatal errors (warnings) |
|
// along the way. The only requirement is that fatal and non-fatal errors can be |
|
// distinguished programmatically; that is a function such as |
|
// |
|
// IsFatal(error) bool |
|
// |
|
// must be implemented. The following is an example of what the above snippet |
|
// could look like using the warnings package: |
|
// |
|
// import "gopkg.in/warnings.v0" |
|
// |
|
// func isFatal(err error) bool { |
|
// _, ok := err.(WarningType) |
|
// return !ok |
|
// } |
|
// |
|
// func myfunc(params) error { |
|
// c := warnings.NewCollector(isFatal) |
|
// c.FatalWithWarnings = true |
|
// if err := c.Collect(doSomething()); err != nil { |
|
// return err |
|
// } |
|
// if err := c.Collect(doSomethingElse(...)); err != nil { |
|
// return err |
|
// } |
|
// if ok := doAnotherThing(...); !ok { |
|
// if err := c.Collect(errors.New("my error")); err != nil { |
|
// return err |
|
// } |
|
// } |
|
// ... |
|
// return c.Done() |
|
// } |
|
// |
|
// For an example of a non-trivial code base using this library, see |
|
// gopkg.in/gcfg.v1 |
|
// |
|
// Rules for using warnings |
|
// |
|
// - ensure that warnings are programmatically distinguishable from fatal |
|
// errors (i.e. implement an isFatal function and any necessary error types) |
|
// - ensure that there is a single Collector instance for a call of each |
|
// exported function |
|
// - ensure that all errors (fatal or warning) are fed through Collect |
|
// - ensure that every time an error is returned, it is one returned by a |
|
// Collector (from Collect or Done) |
|
// - ensure that Collect is never called after Done |
|
// |
|
// TODO |
|
// |
|
// - optionally limit the number of warnings (e.g. stop after 20 warnings) (?) |
|
// - consider interaction with contexts |
|
// - go vet-style invocations verifier |
|
// - semi-automatic code converter |
|
// |
|
package warnings // import "gopkg.in/warnings.v0" |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
) |
|
|
|
// List holds a collection of warnings and optionally one fatal error. |
|
type List struct { |
|
Warnings []error |
|
Fatal error |
|
} |
|
|
|
// Error implements the error interface. |
|
func (l List) Error() string { |
|
b := bytes.NewBuffer(nil) |
|
if l.Fatal != nil { |
|
fmt.Fprintln(b, "fatal:") |
|
fmt.Fprintln(b, l.Fatal) |
|
} |
|
switch len(l.Warnings) { |
|
case 0: |
|
// nop |
|
case 1: |
|
fmt.Fprintln(b, "warning:") |
|
default: |
|
fmt.Fprintln(b, "warnings:") |
|
} |
|
for _, err := range l.Warnings { |
|
fmt.Fprintln(b, err) |
|
} |
|
return b.String() |
|
} |
|
|
|
// A Collector collects errors up to the first fatal error. |
|
type Collector struct { |
|
// IsFatal distinguishes between warnings and fatal errors. |
|
IsFatal func(error) bool |
|
// FatalWithWarnings set to true means that a fatal error is returned as |
|
// a List together with all warnings so far. The default behavior is to |
|
// only return the fatal error and discard any warnings that have been |
|
// collected. |
|
FatalWithWarnings bool |
|
|
|
l List |
|
done bool |
|
} |
|
|
|
// NewCollector returns a new Collector; it uses isFatal to distinguish between |
|
// warnings and fatal errors. |
|
func NewCollector(isFatal func(error) bool) *Collector { |
|
return &Collector{IsFatal: isFatal} |
|
} |
|
|
|
// Collect collects a single error (warning or fatal). It returns nil if |
|
// collection can continue (only warnings so far), or otherwise the errors |
|
// collected. Collect mustn't be called after the first fatal error or after |
|
// Done has been called. |
|
func (c *Collector) Collect(err error) error { |
|
if c.done { |
|
panic("warnings.Collector already done") |
|
} |
|
if err == nil { |
|
return nil |
|
} |
|
if c.IsFatal(err) { |
|
c.done = true |
|
c.l.Fatal = err |
|
} else { |
|
c.l.Warnings = append(c.l.Warnings, err) |
|
} |
|
if c.l.Fatal != nil { |
|
return c.erorr() |
|
} |
|
return nil |
|
} |
|
|
|
// Done ends collection and returns the collected error(s). |
|
func (c *Collector) Done() error { |
|
c.done = true |
|
return c.erorr() |
|
} |
|
|
|
func (c *Collector) erorr() error { |
|
if !c.FatalWithWarnings && c.l.Fatal != nil { |
|
return c.l.Fatal |
|
} |
|
if c.l.Fatal == nil && len(c.l.Warnings) == 0 { |
|
return nil |
|
} |
|
// Note that a single warning is also returned as a List. This is to make it |
|
// easier to determine fatal-ness of the returned error. |
|
return c.l |
|
} |
|
|
|
// FatalOnly returns the fatal error, if any, **in an error returned by a |
|
// Collector**. It returns nil if and only if err is nil or err is a List |
|
// with err.Fatal == nil. |
|
func FatalOnly(err error) error { |
|
l, ok := err.(List) |
|
if !ok { |
|
return err |
|
} |
|
return l.Fatal |
|
} |
|
|
|
// WarningsOnly returns the warnings **in an error returned by a Collector**. |
|
func WarningsOnly(err error) []error { |
|
l, ok := err.(List) |
|
if !ok { |
|
return nil |
|
} |
|
return l.Warnings |
|
}
|
|
|