pax_global_header00006660000000000000000000000064145510777270014531gustar00rootroot0000000000000052 comment=ee974a7c12ea8f7e182c8ddee3206a11f7e5efb8 golang-github-zeebo-errs-1.3.0/000077500000000000000000000000001455107772700163345ustar00rootroot00000000000000golang-github-zeebo-errs-1.3.0/.gitignore000066400000000000000000000000101455107772700203130ustar00rootroot00000000000000.vscode golang-github-zeebo-errs-1.3.0/AUTHORS000066400000000000000000000002061455107772700174020ustar00rootroot00000000000000Egon Elbre Jeff Wendling JT Olio Kaloyan Raev golang-github-zeebo-errs-1.3.0/LICENSE000066400000000000000000000020541455107772700173420ustar00rootroot00000000000000MIT License Copyright (c) 2017 The Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-zeebo-errs-1.3.0/README.md000066400000000000000000000133251455107772700176170ustar00rootroot00000000000000# errs [![GoDoc](https://godoc.org/github.com/zeebo/errs?status.svg)](https://godoc.org/github.com/zeebo/errs) [![Sourcegraph](https://sourcegraph.com/github.com/zeebo/errs/-/badge.svg)](https://sourcegraph.com/github.com/zeebo/errs?badge) [![Go Report Card](https://goreportcard.com/badge/github.com/zeebo/errs)](https://goreportcard.com/report/github.com/zeebo/errs) errs is a package for making errors friendly and easy. ### Creating Errors The easiest way to use it, is to use the package level [New][New] function. It's much like `fmt.Errorf`, but better. For example: ```go func checkThing() error { return errs.New("what's up with %q?", "zeebo") } ``` Why is it better? Errors come with a stack trace that is only printed when a `"+"` character is used in the format string. This should retain the benefits of being able to diagnose where and why errors happen, without all of the noise of printing a stack trace in every situation. For example: ```go func doSomeRealWork() { err := checkThing() if err != nil { fmt.Printf("%+v\n", err) // contains stack trace if it's a errs error. fmt.Printf("%v\n", err) // does not contain a stack trace return } } ``` ### Error Classes You can create a [Class][Class] of errors and check if any error was created by that class. The class name is prefixed to all of the errors it creates. For example: ```go var Unauthorized = errs.Class("unauthorized") func checkUser(username, password string) error { if username != "zeebo" { return Unauthorized.New("who is %q?", username) } if password != "hunter2" { return Unauthorized.New("that's not a good password, jerkmo!") } return nil } func handleRequest() { if err := checkUser("zeebo", "hunter3"); Unauthorized.Has(err) { fmt.Println(err) } // output: // unauthorized: that's not a good password, jerkmo! } ``` Classes can also [Wrap][ClassWrap] other errors, and errors may be wrapped multiple times. For example: ```go var ( Error = errs.Class("mypackage") Unauthorized = errs.Class("unauthorized") ) func deep3() error { return fmt.Errorf("ouch") } func deep2() error { return Unauthorized.Wrap(deep3()) } func deep1() error { return Error.Wrap(deep2()) } func deep() { fmt.Println(deep1()) // output: // mypackage: unauthorized: ouch } ``` In the above example, both `Error.Has(deep1())` and `Unauthorized.Has(deep1())` would return `true`, and the stack trace would only be recorded once at the `deep2` call. In addition, when an error has been wrapped, wrapping it again with the same class will not do anything. For example: ```go func doubleWrap() { fmt.Println(Error.Wrap(Error.New("foo"))) // output: // mypackage: foo } ``` This is to make it an easier decision if you should wrap or not (you should). ### Utilities [Classes][Classes] is a helper function to get a slice of classes that an error has. The latest wrap is first in the slice. For example: ```go func getClasses() { classes := errs.Classes(deep1()) fmt.Println(classes[0] == &Error) fmt.Println(classes[1] == &Unauthorized) // output: // true // true } ``` Finally, a helper function, [Unwrap][Unwrap] is provided to get the wrapped error in cases where you might want to inspect details. For example: ```go var Error = errs.Class("mypackage") func getHandle() (*os.File, error) { fh, err := os.Open("neat_things") if err != nil { return nil, Error.Wrap(err) } return fh, nil } func checkForNeatThings() { fh, err := getHandle() if os.IsNotExist(errs.Unwrap(err)) { panic("no neat things?!") } if err != nil { panic("phew, at least there are neat things, even if i can't see them") } fh.Close() } ``` It knows about both the `Cause() error` and `Unwrap() error` methods that are often used in the community, and will call them as many times as possible. ### Defer The package also provides [WrapP][WrapP] versions of [Wrap][Wrap] that are useful in defer contexts. For example: ```go func checkDefer() (err error) { defer Error.WrapP(&err) fh, err := os.Open("secret_stash") if err != nil { return nil, err } return fh.Close() } ``` ### Groups [Groups][Group] allow one to collect a set of errors. For example: ```go func tonsOfErrors() error { var group errs.Group for _, work := range someWork { group.Add(maybeErrors(work)) } return group.Err() } ``` Some things to note: - The [Add][GroupAdd] method only adds to the group if the passed in error is non-nil. - The [Err][GroupErr] method returns an error only if non-nil errors have been added, and additionally returns just the error if only one error was added. Thus, we always have that if you only call `group.Add(err)`, then `group.Err() == err`. The returned error will format itself similarly: ```go func groupFormat() { var group errs.Group group.Add(errs.New("first")) group.Add(errs.New("second")) err := group.Err() fmt.Printf("%v\n", err) fmt.Println() fmt.Printf("%+v\n", err) // output: // first; second // // group: // --- first // ... stack trace // --- second // ... stack trace } ``` ### Contributing errs is released under an MIT License. If you want to contribute, be sure to add yourself to the list in AUTHORS. [New]: https://godoc.org/github.com/zeebo/errs#New [Wrap]: https://godoc.org/github.com/zeebo/errs#Wrap [WrapP]: https://godoc.org/github.com/zeebo/errs#WrapP [Class]: https://godoc.org/github.com/zeebo/errs#Class [ClassNew]: https://godoc.org/github.com/zeebo/errs#Class.New [ClassWrap]: https://godoc.org/github.com/zeebo/errs#Class.Wrap [Unwrap]: https://godoc.org/github.com/zeebo/errs#Unwrap [Classes]: https://godoc.org/github.com/zeebo/errs#Classes [Group]: https://godoc.org/github.com/zeebo/errs#Group [GroupAdd]: https://godoc.org/github.com/zeebo/errs#Group.Add [GroupErr]: https://godoc.org/github.com/zeebo/errs#Group.Err golang-github-zeebo-errs-1.3.0/errdata/000077500000000000000000000000001455107772700177565ustar00rootroot00000000000000golang-github-zeebo-errs-1.3.0/errdata/README.md000066400000000000000000000032601455107772700212360ustar00rootroot00000000000000# errdata [![GoDoc](https://godoc.org/github.com/zeebo/errs/errdata?status.svg)](https://godoc.org/github.com/zeebo/errs/errdata) [![Sourcegraph](https://sourcegraph.com/github.com/zeebo/errs/-/badge.svg)](https://sourcegraph.com/github.com/zeebo/errs?badge) [![Go Report Card](https://goreportcard.com/badge/github.com/zeebo/errs/errdata)](https://goreportcard.com/report/github.com/zeebo/errs/errdata) errdata helps with associating some data to error classes. ### Adding data The [Set][Set] function associates some data with some error class and key. For example: ```go var ( Unauthorized = errs.Class("unauthorized") NotFound = errs.Class("not found") ) type httpErrorCodeKey struct{} func init() { errdata.Set(Unauthorized, httpErrorCodeKey{}, http.StatusUnauthorized) errdata.Set(NotFound, httpErrorCodeKey{}, http.StatusNotFound) } ``` Why do that? [Get][Get] can read the associated data for an error if it was wrapped by any of the Classes you have set data on. For example: ```go func getStatusCode(err error) int { code, _ := errdata.Get(err, httpErrorCodeKey{}).(int) if code == 0 { code = http.StatusInternalServerError } return code } ``` If the error has been wrapped by multiple classes for that key, the value for the most recently wrapped class is returned. For example: ```go func whatStatusCodeCode() { err := NotFound.Wrap(Unauthorized.New("test")) fmt.Println(getStatusCode(err)) // output: // 404 } ``` ### Contributing errdata is released under an MIT License. If you want to contribute, be sure to add yourself to the list in AUTHORS. [Set]: https://godoc.org/github.com/zeebo/errs/errdata#Set [Get]: https://godoc.org/github.com/zeebo/errs/errdata#Get golang-github-zeebo-errs-1.3.0/errdata/errdata.go000066400000000000000000000022141455107772700217260ustar00rootroot00000000000000// Package errdata helps with associating some data to error classes package errdata import ( "sync" "github.com/zeebo/errs" ) // registry is a concurrent map[key]interface{}. we use this because it is // expected to be frequently read, with a one time initial set of writes. var registry sync.Map // key is the type of keys for the registry map. type key struct { class *errs.Class key interface{} } // makeKey is a helper to create a key for the registry map. func makeKey(class *errs.Class, k interface{}) key { return key{ class: class, key: k, } } // Set associates the value for the given key and class. Errors wrapped by the // class will return the value in the call to Get for the key. func Set(class *errs.Class, key interface{}, value interface{}) { registry.Store(makeKey(class, key), value) } // Get returns the value associated to the key for the error if any of the // classes the error is part of have a value associated for that key. func Get(err error, key interface{}) interface{} { for _, class := range errs.Classes(err) { value, ok := registry.Load(makeKey(class, key)) if ok { return value } } return nil } golang-github-zeebo-errs-1.3.0/errdata/errdata_test.go000066400000000000000000000024211455107772700227650ustar00rootroot00000000000000package errdata import ( "testing" "github.com/zeebo/errs" ) func TestErrdata(t *testing.T) { assert := func(t *testing.T, v bool, err ...interface{}) { t.Helper() if !v { t.Fatal(err...) } } var ( foo = errs.Class("foo") bar = errs.Class("bar") baz = errs.Class("baz") ) type key1 struct{} type key2 struct{} Set(&foo, key1{}, "foo 1") Set(&foo, key2{}, "foo 2") Set(&bar, key1{}, "bar 1") Set(&bar, key2{}, "bar 2") Set(&baz, key1{}, "baz 1") Set(&baz, key2{}, "baz 2") assert(t, Get(errs.New("t"), key1{}) == nil) assert(t, Get(errs.New("t"), key2{}) == nil) assert(t, Get(foo.New("t"), key1{}) == "foo 1") assert(t, Get(foo.New("t"), key2{}) == "foo 2") assert(t, Get(bar.New("t"), key1{}) == "bar 1") assert(t, Get(bar.New("t"), key2{}) == "bar 2") assert(t, Get(baz.New("t"), key1{}) == "baz 1") assert(t, Get(baz.New("t"), key2{}) == "baz 2") assert(t, Get(foo.Wrap(baz.New("t")), key1{}) == "foo 1") assert(t, Get(foo.Wrap(baz.New("t")), key2{}) == "foo 2") assert(t, Get(bar.Wrap(foo.Wrap(baz.New("t"))), key1{}) == "bar 1") assert(t, Get(bar.Wrap(foo.Wrap(baz.New("t"))), key2{}) == "bar 2") Set(&foo, key1{}, nil) Set(&foo, key2{}, nil) assert(t, Get(foo.New("t"), key1{}) == nil) assert(t, Get(foo.New("t"), key2{}) == nil) } golang-github-zeebo-errs-1.3.0/errs.go000066400000000000000000000157561455107772700176540ustar00rootroot00000000000000// Package errs provides a simple error package with stack traces. package errs import ( "fmt" "io" "runtime" ) // Namer is implemented by all errors returned in this package. It returns a // name for the class of error it is, and a boolean indicating if the name is // valid. type Namer interface{ Name() (string, bool) } // Causer is implemented by all errors returned in this package. It returns // the underlying cause of the error, or nil if there is no underlying cause. type Causer interface{ Cause() error } // unwrapper is implemented by all errors returned in this package. It returns // the underlying cause of the error, or nil if there is no underlying error. type unwrapper interface{ Unwrap() error } // ungrouper is implemented by combinedError returned in this package. It // returns all underlying errors, or nil if there is no underlying error. type ungrouper interface{ Ungroup() []error } // New returns an error not contained in any class. This is the same as calling // fmt.Errorf(...) except it captures a stack trace on creation. func New(format string, args ...interface{}) error { return (*Class).create(nil, 3, fmt.Errorf(format, args...)) } // Wrap returns an error not contained in any class. It just associates a stack // trace with the error. Wrap returns nil if err is nil. func Wrap(err error) error { return (*Class).create(nil, 3, err) } // WrapP stores into the error pointer if it contains a non-nil error an error not // contained in any class. It just associates a stack trace with the error. WrapP // does nothing if the pointer or pointed at error is nil. func WrapP(err *error) { if err != nil && *err != nil { *err = (*Class).create(nil, 3, *err) } } // Often, we call Cause as much as possible. Since comparing arbitrary // interfaces with equality isn't panic safe, we only loop up to 100 // times to ensure that a poor implementation that causes a cycle does // not run forever. const maxCause = 100 // Unwrap returns the underlying error, if any, or just the error. func Unwrap(err error) error { for i := 0; err != nil && i < maxCause; i++ { var nerr error switch e := err.(type) { case Causer: nerr = e.Cause() case unwrapper: nerr = e.Unwrap() } if nerr == nil { return err } err = nerr } return err } // Classes returns all the classes that have wrapped the error. func Classes(err error) (classes []*Class) { causes := 0 for { switch e := err.(type) { case *errorT: if e.class != nil { classes = append(classes, e.class) } err = e.err continue case Causer: err = e.Cause() case unwrapper: err = e.Unwrap() default: return classes } if causes >= maxCause { return classes } causes++ } } // Is checks if any of the underlying errors matches target func Is(err, target error) bool { return IsFunc(err, func(err error) bool { return err == target }) } // IsFunc checks if any of the underlying errors matches the func func IsFunc(err error, is func(err error) bool) bool { causes := 0 errs := []error{err} for len(errs) > 0 { var next []error for _, err := range errs { if is(err) { return true } switch e := err.(type) { case ungrouper: ungrouped := e.Ungroup() for _, unerr := range ungrouped { if unerr != nil { next = append(next, unerr) } } case Causer: cause := e.Cause() if cause != nil { next = append(next, cause) } case unwrapper: unwrapped := e.Unwrap() if unwrapped != nil { next = append(next, unwrapped) } } if causes >= maxCause { return false } causes++ } errs = next } return false } // // error classes // // Class represents a class of errors. You can construct errors, and check if // errors are part of the class. type Class string // Has returns true if the passed in error was wrapped by this class. func (c *Class) Has(err error) bool { return IsFunc(err, func(err error) bool { errt, ok := err.(*errorT) return ok && errt.class == c }) } // New constructs an error with the format string that will be contained by // this class. This is the same as calling Wrap(fmt.Errorf(...)). func (c *Class) New(format string, args ...interface{}) error { return c.create(3, fmt.Errorf(format, args...)) } // Wrap returns a new error based on the passed in error that is contained in // this class. Wrap returns nil if err is nil. func (c *Class) Wrap(err error) error { return c.create(3, err) } // WrapP stores into the error pointer if it contains a non-nil error an error contained // in this class. WrapP does nothing if the pointer or pointed at error is nil. func (c *Class) WrapP(err *error) { if err != nil && *err != nil { *err = c.create(3, *err) } } // create constructs the error, or just adds the class to the error, keeping // track of the stack if it needs to construct it. func (c *Class) create(depth int, err error) error { if err == nil { return nil } var pcs []uintptr if err, ok := err.(*errorT); ok { if c == nil || err.class == c { return err } pcs = err.pcs } errt := &errorT{ class: c, err: err, pcs: pcs, } if errt.pcs == nil { errt.pcs = make([]uintptr, 64) n := runtime.Callers(depth, errt.pcs) errt.pcs = errt.pcs[:n:n] } return errt } // // errors // // errorT is the type of errors returned from this package. type errorT struct { class *Class err error pcs []uintptr } var ( // ensure *errorT implements the helper interfaces. _ Namer = (*errorT)(nil) _ Causer = (*errorT)(nil) _ error = (*errorT)(nil) ) // Stack returns the pcs for the stack trace associated with the error. func (e *errorT) Stack() []uintptr { return e.pcs } // errorT implements the error interface. func (e *errorT) Error() string { return fmt.Sprintf("%v", e) } // Format handles the formatting of the error. Using a "+" on the format string // specifier will also write the stack trace. func (e *errorT) Format(f fmt.State, c rune) { sep := "" if e.class != nil && *e.class != "" { fmt.Fprintf(f, "%s", string(*e.class)) sep = ": " } if text := e.err.Error(); len(text) > 0 { fmt.Fprintf(f, "%s%v", sep, text) } if f.Flag(int('+')) { summarizeStack(f, e.pcs) } } // Cause implements the interface wrapping errors are expected to implement // to allow getting at underlying causes. func (e *errorT) Cause() error { return e.err } // Unwrap implements the draft design for error inspection. Since this is // on an unexported type, it should not be hard to maintain going forward // given that it also is the exact same semantics as Cause. func (e *errorT) Unwrap() error { return e.err } // Name returns the name for the error, which is the first wrapping class. func (e *errorT) Name() (string, bool) { if e.class == nil { return "", false } return string(*e.class), true } // summarizeStack writes stack line entries to the writer. func summarizeStack(w io.Writer, pcs []uintptr) { frames := runtime.CallersFrames(pcs) for { frame, more := frames.Next() if !more { return } fmt.Fprintf(w, "\n\t%s:%d", frame.Function, frame.Line) } } golang-github-zeebo-errs-1.3.0/errs_test.go000066400000000000000000000146121455107772700207010ustar00rootroot00000000000000package errs import ( "errors" "fmt" "strings" "sync" "testing" ) type causeError struct{ error } func (c causeError) Cause() error { return c.error } func TestErrs(t *testing.T) { assert := func(t *testing.T, v bool, err ...interface{}) { t.Helper() if !v { t.Fatal(err...) } } var ( foo = Class("foo") bar = Class("bar") baz = Class("baz") empty = Class("") ) t.Run("Class", func(t *testing.T) { t.Run("Has", func(t *testing.T) { assert(t, foo.Has(foo.New("t"))) assert(t, !foo.Has(bar.New("t"))) assert(t, !foo.Has(baz.New("t"))) assert(t, !bar.Has(foo.New("t"))) assert(t, bar.Has(bar.New("t"))) assert(t, !bar.Has(baz.New("t"))) assert(t, foo.Has(bar.Wrap(foo.New("t")))) assert(t, bar.Has(bar.Wrap(foo.New("t")))) assert(t, !baz.Has(bar.Wrap(foo.New("t")))) assert(t, foo.Has(foo.Wrap(bar.New("t")))) assert(t, bar.Has(foo.Wrap(bar.New("t")))) assert(t, !baz.Has(foo.Wrap(bar.New("t")))) }) t.Run("Same Name", func(t *testing.T) { c1 := Class("c") c2 := Class("c") assert(t, c1.Has(c1.New("t"))) assert(t, !c2.Has(c1.New("t"))) assert(t, !c1.Has(c2.New("t"))) assert(t, c2.Has(c2.New("t"))) }) t.Run("Wrap Nil", func(t *testing.T) { assert(t, foo.Wrap(nil) == nil) }) t.Run("WrapP", func(t *testing.T) { err := func() (err error) { defer foo.WrapP(&err) if 1 == 1 { return errors.New("err") } return nil }() t.Logf("%+v", err) assert(t, foo.Has(err)) }) }) t.Run("Error", func(t *testing.T) { t.Run("Format Contains Classes", func(t *testing.T) { assert(t, strings.Contains(foo.New("t").Error(), "foo")) assert(t, strings.Contains(bar.New("t").Error(), "bar")) assert(t, strings.Contains(bar.Wrap(foo.New("t")).Error(), "foo")) assert(t, strings.Contains(bar.Wrap(foo.New("t")).Error(), "bar")) assert(t, strings.Contains(foo.Wrap(bar.New("t")).Error(), "foo")) assert(t, strings.Contains(foo.Wrap(bar.New("t")).Error(), "bar")) }) t.Run("Format With Stack", func(t *testing.T) { err := foo.New("t") assert(t, !strings.Contains(fmt.Sprintf("%v", err), "\n"), "%v format contains newline", ) assert(t, strings.Contains(fmt.Sprintf("%+v", err), "\n"), "%+v format does not contain newline", ) }) t.Run("Unwrap", func(t *testing.T) { err := fmt.Errorf("t") assert(t, nil == Unwrap(nil)) assert(t, err == Unwrap(err)) assert(t, err == Unwrap(foo.Wrap(err))) assert(t, err == Unwrap(bar.Wrap(foo.Wrap(err)))) assert(t, err == Unwrap(causeError{error: err})) // ensure a trivial cycle eventually completes loop := new(causeError) loop.error = loop assert(t, loop == Unwrap(loop)) }) t.Run("Cause", func(t *testing.T) { err := fmt.Errorf("t") assert(t, err == foo.Wrap(err).(*errorT).Cause()) assert(t, err == bar.Wrap(foo.Wrap(err)).(*errorT).Cause().(*errorT).Cause()) }) t.Run("Classes", func(t *testing.T) { err := fmt.Errorf("t") classes := Classes(err) assert(t, classes == nil) err = foo.Wrap(err) classes = Classes(err) assert(t, len(classes) == 1) assert(t, classes[0] == &foo) err = foo.Wrap(err) classes = Classes(err) assert(t, len(classes) == 1) assert(t, classes[0] == &foo) err = bar.Wrap(err) classes = Classes(err) assert(t, len(classes) == 2) assert(t, classes[0] == &bar) assert(t, classes[1] == &foo) err = bar.Wrap(err) classes = Classes(err) assert(t, len(classes) == 2) assert(t, classes[0] == &bar) assert(t, classes[1] == &foo) }) t.Run("Is", func(t *testing.T) { alpha := New("alpha") beta := New("beta") gamma := New("gamma") delta := New("delta") epsilon := New("epsilon") assert(t, Is(nil, nil)) assert(t, !Is(nil, alpha)) assert(t, Is(alpha, alpha)) assert(t, !Is(alpha, beta)) err := Combine( alpha, foo.Wrap(bar.Wrap(baz.Wrap(beta))), bar.Wrap(Combine(gamma, baz.Wrap(delta))), ) assert(t, Is(err, alpha)) assert(t, Is(err, beta)) assert(t, Is(err, gamma)) assert(t, Is(err, delta)) assert(t, !Is(err, epsilon)) }) t.Run("IsFunc", func(t *testing.T) { alpha := New("alpha") beta := New("beta") gamma := New("gamma") delta := New("delta") epsilon := New("epsilon") assert(t, IsFunc(nil, func(err error) bool { return err == nil })) assert(t, !IsFunc(nil, func(err error) bool { return err == alpha })) assert(t, IsFunc(alpha, func(err error) bool { return err == alpha })) assert(t, !IsFunc(alpha, func(err error) bool { return err == beta })) err := Combine( alpha, foo.Wrap(bar.Wrap(baz.Wrap(beta))), bar.Wrap(Combine(gamma, baz.Wrap(delta))), ) assert(t, IsFunc(err, func(err error) bool { return err == alpha })) assert(t, IsFunc(err, func(err error) bool { return err == beta })) assert(t, IsFunc(err, func(err error) bool { return err == gamma })) assert(t, IsFunc(err, func(err error) bool { return err == delta })) assert(t, !IsFunc(err, func(err error) bool { return err == epsilon })) }) t.Run("Name", func(t *testing.T) { name, ok := New("t").(Namer).Name() assert(t, !ok) assert(t, name == "") name, ok = foo.New("t").(Namer).Name() assert(t, ok) assert(t, name == "foo") name, ok = bar.Wrap(foo.New("t")).(Namer).Name() assert(t, ok) assert(t, name == "bar") }) t.Run("Empty String", func(t *testing.T) { assert(t, empty.New("test").Error() == "test") assert(t, foo.Wrap(empty.New("test")).Error() == "foo: test") }) t.Run("Empty Format", func(t *testing.T) { assert(t, empty.New("").Error() == "") assert(t, foo.New("").Error() == "foo") }) t.Run("Immutable", func(t *testing.T) { err := New("") errfoo := foo.Wrap(err) errbar := bar.Wrap(err) assert(t, err.Error() == "") assert(t, errfoo.Error() == "foo") assert(t, errbar.Error() == "bar") }) t.Run("Race", func(t *testing.T) { err := New("race") var wg sync.WaitGroup wg.Add(2) go func() { foo.Wrap(err); wg.Done() }() go func() { bar.Wrap(err); wg.Done() }() wg.Wait() }) }) } func BenchmarkErrs(b *testing.B) { foo := Class("foo") err := errors.New("bench") b.Run("Wrap", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = foo.Wrap(err) } }) b.Run("New", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = foo.New("bench") } }) } golang-github-zeebo-errs-1.3.0/go.mod000066400000000000000000000000461455107772700174420ustar00rootroot00000000000000module github.com/zeebo/errs go 1.12 golang-github-zeebo-errs-1.3.0/group.go000066400000000000000000000042551455107772700200250ustar00rootroot00000000000000package errs import ( "fmt" "io" ) // Group is a list of errors. type Group []error // Combine combines multiple non-empty errors into a single error. func Combine(errs ...error) error { var group Group group.Add(errs...) return group.Err() } // Add adds non-empty errors to the Group. func (group *Group) Add(errs ...error) { for _, err := range errs { if err != nil { *group = append(*group, err) } } } // Err returns an error containing all of the non-nil errors. // If there was only one error, it will return it. // If there were none, it returns nil. func (group Group) Err() error { sanitized := group.sanitize() if len(sanitized) == 0 { return nil } if len(sanitized) == 1 { return sanitized[0] } return combinedError(sanitized) } // sanitize returns group that doesn't contain nil-s func (group Group) sanitize() Group { // sanity check for non-nil errors for i, err := range group { if err == nil { sanitized := make(Group, 0, len(group)-1) sanitized = append(sanitized, group[:i]...) sanitized.Add(group[i+1:]...) return sanitized } } return group } // combinedError is a list of non-empty errors type combinedError []error // Cause returns the first error. func (group combinedError) Cause() error { if len(group) > 0 { return group[0] } return nil } // Unwrap returns the first error. func (group combinedError) Unwrap() error { return group.Cause() } // Ungroup returns all errors. func (group combinedError) Ungroup() []error { return group } // Error returns error string delimited by semicolons. func (group combinedError) Error() string { return fmt.Sprintf("%v", group) } // Format handles the formatting of the error. Using a "+" on the format // string specifier will cause the errors to be formatted with "+" and // delimited by newlines. They are delimited by semicolons otherwise. func (group combinedError) Format(f fmt.State, c rune) { delim := "; " if f.Flag(int('+')) { io.WriteString(f, "group:\n--- ") delim = "\n--- " } for i, err := range group { if i != 0 { io.WriteString(f, delim) } if formatter, ok := err.(fmt.Formatter); ok { formatter.Format(f, c) } else { fmt.Fprintf(f, "%v", err) } } } golang-github-zeebo-errs-1.3.0/group_test.go000066400000000000000000000014141455107772700210560ustar00rootroot00000000000000package errs import ( "fmt" "strings" "testing" ) func TestGroup(t *testing.T) { alpha := New("alpha") beta := New("beta") var group Group group.Add(nil, nil, nil) if group.Err() != nil { t.Fatal("expected nil") } group.Add(alpha) if group.Err() != alpha { t.Fatal("expected alpha") } group.Add(nil, beta) if group.Err().Error() != "alpha; beta" { t.Fatal("expected \"group: alpha; beta\"") } if fmt.Sprintf("%v", group.Err()) != "alpha; beta" { t.Fatal("expected \"group: alpha; beta\"") } if strings.Count(fmt.Sprintf("%+v", group.Err()), "\n") <= 1 { t.Fatal("expected multiple lines with +v") } t.Logf("%%v:\n%v", group.Err()) t.Logf("%%+v:\n%+v", group.Err()) if Unwrap(group.Err()) != Unwrap(alpha) { t.Fatal("expected alpha") } }