pax_global_header00006660000000000000000000000064152024276450014521gustar00rootroot0000000000000052 comment=d2f4f56a0bcf943eab94c3f15b54c98f63313d06 golang-go.uber-multierr-1.11.0/000077500000000000000000000000001520242764500162505ustar00rootroot00000000000000golang-go.uber-multierr-1.11.0/.codecov.yml000066400000000000000000000013731520242764500204770ustar00rootroot00000000000000coverage: range: 80..100 round: down precision: 2 status: project: # measuring the overall project coverage default: # context, you can create multiple ones with custom titles enabled: yes # must be yes|true to enable this status target: 100 # specify the target coverage for each commit status # option: "auto" (must increase from parent commit or pull request base) # option: "X%" a static target percentage to hit if_not_found: success # if parent is not found report status as success, error, or failure if_ci_failed: error # if ci fails report status as success, error, or failure golang-go.uber-multierr-1.11.0/.github/000077500000000000000000000000001520242764500176105ustar00rootroot00000000000000golang-go.uber-multierr-1.11.0/.github/workflows/000077500000000000000000000000001520242764500216455ustar00rootroot00000000000000golang-go.uber-multierr-1.11.0/.github/workflows/fossa.yaml000066400000000000000000000005411520242764500236440ustar00rootroot00000000000000name: FOSSA Analysis on: push permissions: contents: read jobs: build: runs-on: ubuntu-latest if: github.repository_owner == 'uber-go' steps: - name: Checkout code uses: actions/checkout@v2 - name: FOSSA analysis uses: fossas/fossa-action@v1 with: api-key: ${{ secrets.FOSSA_API_KEY }} golang-go.uber-multierr-1.11.0/.github/workflows/go.yml000066400000000000000000000016471520242764500230050ustar00rootroot00000000000000name: Go on: push: branches: ['*'] tags: ['v*'] pull_request: branches: ['*'] permissions: contents: read jobs: build: runs-on: ubuntu-latest strategy: matrix: go: ["1.19.x", "1.20.x"] include: - go: 1.20.x latest: true steps: - name: Setup Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - name: Checkout code uses: actions/checkout@v2 - name: Load cached dependencies uses: actions/cache@v1 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Download Dependencies run: go mod download - name: Lint if: matrix.latest run: make lint - name: Test run: make cover - name: Upload coverage to codecov.io uses: codecov/codecov-action@v1 golang-go.uber-multierr-1.11.0/.gitignore000066400000000000000000000000421520242764500202340ustar00rootroot00000000000000/vendor cover.html cover.out /bin golang-go.uber-multierr-1.11.0/CHANGELOG.md000066400000000000000000000036301520242764500200630ustar00rootroot00000000000000Releases ======== v1.11.0 (2023-03-28) ==================== - `Errors` now supports any error that implements multiple-error interface. - Add `Every` function to allow checking if all errors in the chain satisfies `errors.Is` against the target error. v1.10.0 (2023-03-08) ==================== - Comply with Go 1.20's multiple-error interface. - Drop Go 1.18 support. Per the support policy, only Go 1.19 and 1.20 are supported now. - Drop all non-test external dependencies. v1.9.0 (2022-12-12) =================== - Add `AppendFunc` that allow passsing functions to similar to `AppendInvoke`. - Bump up yaml.v3 dependency to 3.0.1. v1.8.0 (2022-02-28) =================== - `Combine`: perform zero allocations when there are no errors. v1.7.0 (2021-05-06) =================== - Add `AppendInvoke` to append into errors from `defer` blocks. v1.6.0 (2020-09-14) =================== - Actually drop library dependency on development-time tooling. v1.5.0 (2020-02-24) =================== - Drop library dependency on development-time tooling. v1.4.0 (2019-11-04) =================== - Add `AppendInto` function to more ergonomically build errors inside a loop. v1.3.0 (2019-10-29) =================== - Switch to Go modules. v1.2.0 (2019-09-26) =================== - Support extracting and matching against wrapped errors with `errors.As` and `errors.Is`. v1.1.0 (2017-06-30) =================== - Added an `Errors(error) []error` function to extract the underlying list of errors for a multierr error. v1.0.0 (2017-05-31) =================== No changes since v0.2.0. This release is committing to making no breaking changes to the current API in the 1.X series. v0.2.0 (2017-04-11) =================== - Repeatedly appending to the same error is now faster due to fewer allocations. v0.1.0 (2017-31-03) =================== - Initial release golang-go.uber-multierr-1.11.0/LICENSE.txt000066400000000000000000000020601520242764500200710ustar00rootroot00000000000000Copyright (c) 2017-2021 Uber Technologies, Inc. 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-go.uber-multierr-1.11.0/Makefile000066400000000000000000000015521520242764500177130ustar00rootroot00000000000000# Directory to put `go install`ed binaries in. export GOBIN ?= $(shell pwd)/bin GO_FILES := $(shell \ find . '(' -path '*/.*' -o -path './vendor' ')' -prune \ -o -name '*.go' -print | cut -b3-) .PHONY: build build: go build ./... .PHONY: test test: go test -race ./... .PHONY: gofmt gofmt: $(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX)) @gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true @[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" | cat - $(FMT_LOG) && false) .PHONY: golint golint: @cd tools && go install golang.org/x/lint/golint @$(GOBIN)/golint ./... .PHONY: staticcheck staticcheck: @cd tools && go install honnef.co/go/tools/cmd/staticcheck @$(GOBIN)/staticcheck ./... .PHONY: lint lint: gofmt golint staticcheck .PHONY: cover cover: go test -race -coverprofile=cover.out -coverpkg=./... -v ./... go tool cover -html=cover.out -o cover.html golang-go.uber-multierr-1.11.0/README.md000066400000000000000000000030421520242764500175260ustar00rootroot00000000000000# multierr [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] `multierr` allows combining one or more Go `error`s together. ## Features - **Idiomatic**: multierr follows best practices in Go, and keeps your code idiomatic. - It keeps the underlying error type hidden, allowing you to deal in `error` values exclusively. - It provides APIs to safely append into an error from a `defer` statement. - **Performant**: multierr is optimized for performance: - It avoids allocations where possible. - It utilizes slice resizing semantics to optimize common cases like appending into the same error object from a loop. - **Interoperable**: multierr interoperates with the Go standard library's error APIs seamlessly: - The `errors.Is` and `errors.As` functions *just work*. - **Lightweight**: multierr comes with virtually no dependencies. ## Installation ```bash go get -u go.uber.org/multierr@latest ``` ## Status Stable: No breaking changes will be made before 2.0. ------------------------------------------------------------------------------- Released under the [MIT License]. [MIT License]: LICENSE.txt [doc-img]: https://pkg.go.dev/badge/go.uber.org/multierr [doc]: https://pkg.go.dev/go.uber.org/multierr [ci-img]: https://github.com/uber-go/multierr/actions/workflows/go.yml/badge.svg [cov-img]: https://codecov.io/gh/uber-go/multierr/branch/master/graph/badge.svg [ci]: https://github.com/uber-go/multierr/actions/workflows/go.yml [cov]: https://codecov.io/gh/uber-go/multierr golang-go.uber-multierr-1.11.0/appendinvoke_example_test.go000066400000000000000000000044471520242764500240450ustar00rootroot00000000000000// Copyright (c) 2021 Uber Technologies, Inc. // // 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. package multierr_test import ( "fmt" "log" "os" "path/filepath" "go.uber.org/multierr" ) func ExampleAppendInvoke() { if err := run(); err != nil { log.Fatal(err) } } func run() (err error) { dir, err := os.MkdirTemp("", "multierr") // We create a temporary directory and defer its deletion when this // function returns. // // If we failed to delete the temporary directory, we append its // failure into the returned value with multierr.AppendInvoke. // // This uses a custom invoker that we implement below. defer multierr.AppendInvoke(&err, RemoveAll(dir)) path := filepath.Join(dir, "example.txt") f, err := os.Create(path) if err != nil { return err } // Similarly, we defer closing the open file when the function returns, // and appends its failure, if any, into the returned error. // // This uses the multierr.Close invoker included in multierr. defer multierr.AppendInvoke(&err, multierr.Close(f)) if _, err := fmt.Fprintln(f, "hello"); err != nil { return err } return nil } // RemoveAll is a multierr.Invoker that deletes the provided directory and all // of its contents. type RemoveAll string func (r RemoveAll) Invoke() error { return os.RemoveAll(string(r)) } golang-go.uber-multierr-1.11.0/benchmarks_test.go000066400000000000000000000061031520242764500217530ustar00rootroot00000000000000// Copyright (c) 2017 Uber Technologies, Inc. // // 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. package multierr import ( "errors" "fmt" "testing" ) func BenchmarkAppend(b *testing.B) { errorTypes := []struct { name string err error }{ { name: "nil", err: nil, }, { name: "single error", err: errors.New("test"), }, { name: "multiple errors", err: appendN(nil, errors.New("err"), 10), }, } for _, initial := range errorTypes { for _, v := range errorTypes { msg := fmt.Sprintf("append %v to %v", v.name, initial.name) b.Run(msg, func(b *testing.B) { for _, appends := range []int{1, 2, 10} { b.Run(fmt.Sprint(appends), func(b *testing.B) { for i := 0; i < b.N; i++ { appendN(initial.err, v.err, appends) } }) } }) } } } func BenchmarkCombine(b *testing.B) { b.Run("inline 1", func(b *testing.B) { var x error for i := 0; i < b.N; i++ { Combine(x) } }) b.Run("inline 2", func(b *testing.B) { var x, y error for i := 0; i < b.N; i++ { Combine(x, y) } }) b.Run("inline 3 no error", func(b *testing.B) { var x, y, z error for i := 0; i < b.N; i++ { Combine(x, y, z) } }) b.Run("inline 3 one error", func(b *testing.B) { var x, y, z error z = fmt.Errorf("failed") for i := 0; i < b.N; i++ { Combine(x, y, z) } }) b.Run("inline 3 multiple errors", func(b *testing.B) { var x, y, z error z = fmt.Errorf("failed3") y = fmt.Errorf("failed2") x = fmt.Errorf("failed") for i := 0; i < b.N; i++ { Combine(x, y, z) } }) b.Run("slice 100 no errors", func(b *testing.B) { errs := make([]error, 100) for i := 0; i < b.N; i++ { Combine(errs...) } }) b.Run("slice 100 one error", func(b *testing.B) { errs := make([]error, 100) errs[len(errs)-1] = fmt.Errorf("failed") for i := 0; i < b.N; i++ { Combine(errs...) } }) b.Run("slice 100 multi error", func(b *testing.B) { errs := make([]error, 100) errs[0] = fmt.Errorf("failed1") errs[len(errs)-1] = fmt.Errorf("failed2") for i := 0; i < b.N; i++ { Combine(errs...) } }) } golang-go.uber-multierr-1.11.0/error.go000066400000000000000000000426371520242764500177440ustar00rootroot00000000000000// Copyright (c) 2017-2023 Uber Technologies, Inc. // // 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. // Package multierr allows combining one or more errors together. // // # Overview // // Errors can be combined with the use of the Combine function. // // multierr.Combine( // reader.Close(), // writer.Close(), // conn.Close(), // ) // // If only two errors are being combined, the Append function may be used // instead. // // err = multierr.Append(reader.Close(), writer.Close()) // // The underlying list of errors for a returned error object may be retrieved // with the Errors function. // // errors := multierr.Errors(err) // if len(errors) > 0 { // fmt.Println("The following errors occurred:", errors) // } // // # Appending from a loop // // You sometimes need to append into an error from a loop. // // var err error // for _, item := range items { // err = multierr.Append(err, process(item)) // } // // Cases like this may require knowledge of whether an individual instance // failed. This usually requires introduction of a new variable. // // var err error // for _, item := range items { // if perr := process(item); perr != nil { // log.Warn("skipping item", item) // err = multierr.Append(err, perr) // } // } // // multierr includes AppendInto to simplify cases like this. // // var err error // for _, item := range items { // if multierr.AppendInto(&err, process(item)) { // log.Warn("skipping item", item) // } // } // // This will append the error into the err variable, and return true if that // individual error was non-nil. // // See [AppendInto] for more information. // // # Deferred Functions // // Go makes it possible to modify the return value of a function in a defer // block if the function was using named returns. This makes it possible to // record resource cleanup failures from deferred blocks. // // func sendRequest(req Request) (err error) { // conn, err := openConnection() // if err != nil { // return err // } // defer func() { // err = multierr.Append(err, conn.Close()) // }() // // ... // } // // multierr provides the Invoker type and AppendInvoke function to make cases // like the above simpler and obviate the need for a closure. The following is // roughly equivalent to the example above. // // func sendRequest(req Request) (err error) { // conn, err := openConnection() // if err != nil { // return err // } // defer multierr.AppendInvoke(&err, multierr.Close(conn)) // // ... // } // // See [AppendInvoke] and [Invoker] for more information. // // NOTE: If you're modifying an error from inside a defer, you MUST use a named // return value for that function. // // # Advanced Usage // // Errors returned by Combine and Append MAY implement the following // interface. // // type errorGroup interface { // // Returns a slice containing the underlying list of errors. // // // // This slice MUST NOT be modified by the caller. // Errors() []error // } // // Note that if you need access to list of errors behind a multierr error, you // should prefer using the Errors function. That said, if you need cheap // read-only access to the underlying errors slice, you can attempt to cast // the error to this interface. You MUST handle the failure case gracefully // because errors returned by Combine and Append are not guaranteed to // implement this interface. // // var errors []error // group, ok := err.(errorGroup) // if ok { // errors = group.Errors() // } else { // errors = []error{err} // } package multierr // import "go.uber.org/multierr" import ( "bytes" "errors" "fmt" "io" "strings" "sync" "sync/atomic" ) var ( // Separator for single-line error messages. _singlelineSeparator = []byte("; ") // Prefix for multi-line messages _multilinePrefix = []byte("the following errors occurred:") // Prefix for the first and following lines of an item in a list of // multi-line error messages. // // For example, if a single item is: // // foo // bar // // It will become, // // - foo // bar _multilineSeparator = []byte("\n - ") _multilineIndent = []byte(" ") ) // _bufferPool is a pool of bytes.Buffers. var _bufferPool = sync.Pool{ New: func() interface{} { return &bytes.Buffer{} }, } type errorGroup interface { Errors() []error } // Errors returns a slice containing zero or more errors that the supplied // error is composed of. If the error is nil, a nil slice is returned. // // err := multierr.Append(r.Close(), w.Close()) // errors := multierr.Errors(err) // // If the error is not composed of other errors, the returned slice contains // just the error that was passed in. // // Callers of this function are free to modify the returned slice. func Errors(err error) []error { return extractErrors(err) } // multiError is an error that holds one or more errors. // // An instance of this is guaranteed to be non-empty and flattened. That is, // none of the errors inside multiError are other multiErrors. // // multiError formats to a semi-colon delimited list of error messages with // %v and with a more readable multi-line format with %+v. type multiError struct { copyNeeded atomic.Bool errors []error } // Errors returns the list of underlying errors. // // This slice MUST NOT be modified. func (merr *multiError) Errors() []error { if merr == nil { return nil } return merr.errors } func (merr *multiError) Error() string { if merr == nil { return "" } buff := _bufferPool.Get().(*bytes.Buffer) buff.Reset() merr.writeSingleline(buff) result := buff.String() _bufferPool.Put(buff) return result } // Every compares every error in the given err against the given target error // using [errors.Is], and returns true only if every comparison returned true. func Every(err error, target error) bool { for _, e := range extractErrors(err) { if !errors.Is(e, target) { return false } } return true } func (merr *multiError) Format(f fmt.State, c rune) { if c == 'v' && f.Flag('+') { merr.writeMultiline(f) } else { merr.writeSingleline(f) } } func (merr *multiError) writeSingleline(w io.Writer) { first := true for _, item := range merr.errors { if first { first = false } else { w.Write(_singlelineSeparator) } io.WriteString(w, item.Error()) } } func (merr *multiError) writeMultiline(w io.Writer) { w.Write(_multilinePrefix) for _, item := range merr.errors { w.Write(_multilineSeparator) writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item)) } } // Writes s to the writer with the given prefix added before each line after // the first. func writePrefixLine(w io.Writer, prefix []byte, s string) { first := true for len(s) > 0 { if first { first = false } else { w.Write(prefix) } idx := strings.IndexByte(s, '\n') if idx < 0 { idx = len(s) - 1 } io.WriteString(w, s[:idx+1]) s = s[idx+1:] } } type inspectResult struct { // Number of top-level non-nil errors Count int // Total number of errors including multiErrors Capacity int // Index of the first non-nil error in the list. Value is meaningless if // Count is zero. FirstErrorIdx int // Whether the list contains at least one multiError ContainsMultiError bool } // Inspects the given slice of errors so that we can efficiently allocate // space for it. func inspect(errors []error) (res inspectResult) { first := true for i, err := range errors { if err == nil { continue } res.Count++ if first { first = false res.FirstErrorIdx = i } if merr, ok := err.(*multiError); ok { res.Capacity += len(merr.errors) res.ContainsMultiError = true } else { res.Capacity++ } } return } // fromSlice converts the given list of errors into a single error. func fromSlice(errors []error) error { // Don't pay to inspect small slices. switch len(errors) { case 0: return nil case 1: return errors[0] } res := inspect(errors) switch res.Count { case 0: return nil case 1: // only one non-nil entry return errors[res.FirstErrorIdx] case len(errors): if !res.ContainsMultiError { // Error list is flat. Make a copy of it // Otherwise "errors" escapes to the heap // unconditionally for all other cases. // This lets us optimize for the "no errors" case. out := append(([]error)(nil), errors...) return &multiError{errors: out} } } nonNilErrs := make([]error, 0, res.Capacity) for _, err := range errors[res.FirstErrorIdx:] { if err == nil { continue } if nested, ok := err.(*multiError); ok { nonNilErrs = append(nonNilErrs, nested.errors...) } else { nonNilErrs = append(nonNilErrs, err) } } return &multiError{errors: nonNilErrs} } // Combine combines the passed errors into a single error. // // If zero arguments were passed or if all items are nil, a nil error is // returned. // // Combine(nil, nil) // == nil // // If only a single error was passed, it is returned as-is. // // Combine(err) // == err // // Combine skips over nil arguments so this function may be used to combine // together errors from operations that fail independently of each other. // // multierr.Combine( // reader.Close(), // writer.Close(), // pipe.Close(), // ) // // If any of the passed errors is a multierr error, it will be flattened along // with the other errors. // // multierr.Combine(multierr.Combine(err1, err2), err3) // // is the same as // multierr.Combine(err1, err2, err3) // // The returned error formats into a readable multi-line error message if // formatted with %+v. // // fmt.Sprintf("%+v", multierr.Combine(err1, err2)) func Combine(errors ...error) error { return fromSlice(errors) } // Append appends the given errors together. Either value may be nil. // // This function is a specialization of Combine for the common case where // there are only two errors. // // err = multierr.Append(reader.Close(), writer.Close()) // // The following pattern may also be used to record failure of deferred // operations without losing information about the original error. // // func doSomething(..) (err error) { // f := acquireResource() // defer func() { // err = multierr.Append(err, f.Close()) // }() // // Note that the variable MUST be a named return to append an error to it from // the defer statement. See also [AppendInvoke]. func Append(left error, right error) error { switch { case left == nil: return right case right == nil: return left } if _, ok := right.(*multiError); !ok { if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) { // Common case where the error on the left is constantly being // appended to. errs := append(l.errors, right) return &multiError{errors: errs} } else if !ok { // Both errors are single errors. return &multiError{errors: []error{left, right}} } } // Either right or both, left and right, are multiErrors. Rely on usual // expensive logic. errors := [2]error{left, right} return fromSlice(errors[0:]) } // AppendInto appends an error into the destination of an error pointer and // returns whether the error being appended was non-nil. // // var err error // multierr.AppendInto(&err, r.Close()) // multierr.AppendInto(&err, w.Close()) // // The above is equivalent to, // // err := multierr.Append(r.Close(), w.Close()) // // As AppendInto reports whether the provided error was non-nil, it may be // used to build a multierr error in a loop more ergonomically. For example: // // var err error // for line := range lines { // var item Item // if multierr.AppendInto(&err, parse(line, &item)) { // continue // } // items = append(items, item) // } // // Compare this with a version that relies solely on Append: // // var err error // for line := range lines { // var item Item // if parseErr := parse(line, &item); parseErr != nil { // err = multierr.Append(err, parseErr) // continue // } // items = append(items, item) // } func AppendInto(into *error, err error) (errored bool) { if into == nil { // We panic if 'into' is nil. This is not documented above // because suggesting that the pointer must be non-nil may // confuse users into thinking that the error that it points // to must be non-nil. panic("misuse of multierr.AppendInto: into pointer must not be nil") } if err == nil { return false } *into = Append(*into, err) return true } // Invoker is an operation that may fail with an error. Use it with // AppendInvoke to append the result of calling the function into an error. // This allows you to conveniently defer capture of failing operations. // // See also, [Close] and [Invoke]. type Invoker interface { Invoke() error } // Invoke wraps a function which may fail with an error to match the Invoker // interface. Use it to supply functions matching this signature to // AppendInvoke. // // For example, // // func processReader(r io.Reader) (err error) { // scanner := bufio.NewScanner(r) // defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err)) // for scanner.Scan() { // // ... // } // // ... // } // // In this example, the following line will construct the Invoker right away, // but defer the invocation of scanner.Err() until the function returns. // // defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err)) // // Note that the error you're appending to from the defer statement MUST be a // named return. type Invoke func() error // Invoke calls the supplied function and returns its result. func (i Invoke) Invoke() error { return i() } // Close builds an Invoker that closes the provided io.Closer. Use it with // AppendInvoke to close io.Closers and append their results into an error. // // For example, // // func processFile(path string) (err error) { // f, err := os.Open(path) // if err != nil { // return err // } // defer multierr.AppendInvoke(&err, multierr.Close(f)) // return processReader(f) // } // // In this example, multierr.Close will construct the Invoker right away, but // defer the invocation of f.Close until the function returns. // // defer multierr.AppendInvoke(&err, multierr.Close(f)) // // Note that the error you're appending to from the defer statement MUST be a // named return. func Close(closer io.Closer) Invoker { return Invoke(closer.Close) } // AppendInvoke appends the result of calling the given Invoker into the // provided error pointer. Use it with named returns to safely defer // invocation of fallible operations until a function returns, and capture the // resulting errors. // // func doSomething(...) (err error) { // // ... // f, err := openFile(..) // if err != nil { // return err // } // // // multierr will call f.Close() when this function returns and // // if the operation fails, its append its error into the // // returned error. // defer multierr.AppendInvoke(&err, multierr.Close(f)) // // scanner := bufio.NewScanner(f) // // Similarly, this scheduled scanner.Err to be called and // // inspected when the function returns and append its error // // into the returned error. // defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err)) // // // ... // } // // NOTE: If used with a defer, the error variable MUST be a named return. // // Without defer, AppendInvoke behaves exactly like AppendInto. // // err := // ... // multierr.AppendInvoke(&err, mutltierr.Invoke(foo)) // // // ...is roughly equivalent to... // // err := // ... // multierr.AppendInto(&err, foo()) // // The advantage of the indirection introduced by Invoker is to make it easy // to defer the invocation of a function. Without this indirection, the // invoked function will be evaluated at the time of the defer block rather // than when the function returns. // // // BAD: This is likely not what the caller intended. This will evaluate // // foo() right away and append its result into the error when the // // function returns. // defer multierr.AppendInto(&err, foo()) // // // GOOD: This will defer invocation of foo unutil the function returns. // defer multierr.AppendInvoke(&err, multierr.Invoke(foo)) // // multierr provides a few Invoker implementations out of the box for // convenience. See [Invoker] for more information. func AppendInvoke(into *error, invoker Invoker) { AppendInto(into, invoker.Invoke()) } // AppendFunc is a shorthand for [AppendInvoke]. // It allows using function or method value directly // without having to wrap it into an [Invoker] interface. // // func doSomething(...) (err error) { // w, err := startWorker(...) // if err != nil { // return err // } // // // multierr will call w.Stop() when this function returns and // // if the operation fails, it appends its error into the // // returned error. // defer multierr.AppendFunc(&err, w.Stop) // } func AppendFunc(into *error, fn func() error) { AppendInvoke(into, Invoke(fn)) } golang-go.uber-multierr-1.11.0/error_ext_test.go000066400000000000000000000074511520242764500216560ustar00rootroot00000000000000// Copyright (c) 2020 Uber Technologies, Inc. // // 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. package multierr_test import ( "errors" "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/multierr" ) type errGreatSadness struct{ id int } func (errGreatSadness) Error() string { return "great sadness" } type errUnprecedentedFailure struct{ id int } func (errUnprecedentedFailure) Error() string { return "unprecedented failure" } func (e errUnprecedentedFailure) Unwrap() error { return errRootCause{e.id} } type errRootCause struct{ i int } func (errRootCause) Error() string { return "root cause" } func TestErrorsWrapping(t *testing.T) { err := multierr.Append( errGreatSadness{42}, errUnprecedentedFailure{43}, ) t.Run("left", func(t *testing.T) { t.Run("As", func(t *testing.T) { var got errGreatSadness require.True(t, errors.As(err, &got)) assert.Equal(t, 42, got.id) }) t.Run("Is", func(t *testing.T) { assert.False(t, errors.Is(err, errGreatSadness{41})) assert.True(t, errors.Is(err, errGreatSadness{42})) }) }) t.Run("right", func(t *testing.T) { t.Run("As", func(t *testing.T) { var got errUnprecedentedFailure require.True(t, errors.As(err, &got)) assert.Equal(t, 43, got.id) }) t.Run("Is", func(t *testing.T) { assert.False(t, errors.Is(err, errUnprecedentedFailure{42})) assert.True(t, errors.Is(err, errUnprecedentedFailure{43})) }) }) t.Run("top-level", func(t *testing.T) { t.Run("As", func(t *testing.T) { var got interface{ Errors() []error } require.True(t, errors.As(err, &got)) assert.Len(t, got.Errors(), 2) }) t.Run("Is", func(t *testing.T) { assert.True(t, errors.Is(err, err)) }) }) t.Run("root cause", func(t *testing.T) { t.Run("As", func(t *testing.T) { var got errRootCause require.True(t, errors.As(err, &got)) assert.Equal(t, 43, got.i) }) t.Run("Is", func(t *testing.T) { assert.False(t, errors.Is(err, errRootCause{42})) assert.True(t, errors.Is(err, errRootCause{43})) }) }) t.Run("mismatch", func(t *testing.T) { t.Run("As", func(t *testing.T) { var got *os.PathError assert.False(t, errors.As(err, &got)) }) t.Run("Is", func(t *testing.T) { assert.False(t, errors.Is(err, errors.New("great sadness"))) }) }) } func TestErrorsWrappingSameType(t *testing.T) { err := multierr.Combine( errGreatSadness{1}, errGreatSadness{2}, errGreatSadness{3}, ) t.Run("As returns first", func(t *testing.T) { var got errGreatSadness require.True(t, errors.As(err, &got)) assert.Equal(t, 1, got.id) }) t.Run("Is matches all", func(t *testing.T) { assert.True(t, errors.Is(err, errGreatSadness{1})) assert.True(t, errors.Is(err, errGreatSadness{2})) assert.True(t, errors.Is(err, errGreatSadness{3})) }) } golang-go.uber-multierr-1.11.0/error_post_go120.go000066400000000000000000000031621520242764500217070ustar00rootroot00000000000000// Copyright (c) 2017-2023 Uber Technologies, Inc. // // 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. //go:build go1.20 // +build go1.20 package multierr // Unwrap returns a list of errors wrapped by this multierr. func (merr *multiError) Unwrap() []error { return merr.Errors() } type multipleErrors interface { Unwrap() []error } func extractErrors(err error) []error { if err == nil { return nil } // check if the given err is an Unwrapable error that // implements multipleErrors interface. eg, ok := err.(multipleErrors) if !ok { return []error{err} } return append(([]error)(nil), eg.Unwrap()...) } golang-go.uber-multierr-1.11.0/error_post_go120_test.go000066400000000000000000000041561520242764500227520ustar00rootroot00000000000000// Copyright (c) 2023 Uber Technologies, Inc. // // 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. //go:build go1.20 // +build go1.20 package multierr import ( "errors" "testing" "github.com/stretchr/testify/assert" ) func TestErrorsOnErrorsJoin(t *testing.T) { err1 := errors.New("err1") err2 := errors.New("err2") err := errors.Join(err1, err2) errs := Errors(err) assert.Equal(t, 2, len(errs)) assert.Equal(t, err1, errs[0]) assert.Equal(t, err2, errs[1]) } func TestEveryWithErrorsJoin(t *testing.T) { myError1 := errors.New("woeful misfortune") myError2 := errors.New("worrisome travesty") t.Run("all match", func(t *testing.T) { err := errors.Join(myError1, myError1, myError1) assert.True(t, errors.Is(err, myError1)) assert.True(t, Every(err, myError1)) assert.False(t, errors.Is(err, myError2)) assert.False(t, Every(err, myError2)) }) t.Run("one matches", func(t *testing.T) { err := errors.Join(myError1, myError2) assert.True(t, errors.Is(err, myError1)) assert.False(t, Every(err, myError1)) assert.True(t, errors.Is(err, myError2)) assert.False(t, Every(err, myError2)) }) } golang-go.uber-multierr-1.11.0/error_pre_go120.go000066400000000000000000000052321520242764500215100ustar00rootroot00000000000000// Copyright (c) 2017-2023 Uber Technologies, Inc. // // 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. //go:build !go1.20 // +build !go1.20 package multierr import "errors" // Versions of Go before 1.20 did not support the Unwrap() []error method. // This provides a similar behavior by implementing the Is(..) and As(..) // methods. // See the errors.Join proposal for details: // https://github.com/golang/go/issues/53435 // As attempts to find the first error in the error list that matches the type // of the value that target points to. // // This function allows errors.As to traverse the values stored on the // multierr error. func (merr *multiError) As(target interface{}) bool { for _, err := range merr.Errors() { if errors.As(err, target) { return true } } return false } // Is attempts to match the provided error against errors in the error list. // // This function allows errors.Is to traverse the values stored on the // multierr error. func (merr *multiError) Is(target error) bool { for _, err := range merr.Errors() { if errors.Is(err, target) { return true } } return false } func extractErrors(err error) []error { if err == nil { return nil } // Note that we're casting to multiError, not errorGroup. Our contract is // that returned errors MAY implement errorGroup. Errors, however, only // has special behavior for multierr-specific error objects. // // This behavior can be expanded in the future but I think it's prudent to // start with as little as possible in terms of contract and possibility // of misuse. eg, ok := err.(*multiError) if !ok { return []error{err} } return append(([]error)(nil), eg.Errors()...) } golang-go.uber-multierr-1.11.0/error_test.go000066400000000000000000000405661520242764500210020ustar00rootroot00000000000000// Copyright (c) 2017-2021 Uber Technologies, Inc. // // 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. package multierr import ( "errors" "fmt" "io" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // richFormatError is an error that prints a different output depending on // whether %v or %+v was used. type richFormatError struct{} func (r richFormatError) Error() string { return fmt.Sprint(r) } func (richFormatError) Format(f fmt.State, c rune) { if c == 'v' && f.Flag('+') { io.WriteString(f, "multiline\nmessage\nwith plus") } else { io.WriteString(f, "without plus") } } func appendN(initial, err error, n int) error { errs := initial for i := 0; i < n; i++ { errs = Append(errs, err) } return errs } func newMultiErr(errors ...error) error { return &multiError{errors: errors} } func TestEvery(t *testing.T) { myError1 := errors.New("woeful misfortune") myError2 := errors.New("worrisome travesty") for _, tt := range []struct { desc string giveErr error giveTarget error wantIs bool wantEvery bool }{ { desc: "all match", giveErr: newMultiErr(myError1, myError1, myError1), giveTarget: myError1, wantIs: true, wantEvery: true, }, { desc: "one matches", giveErr: newMultiErr(myError1, myError2), giveTarget: myError1, wantIs: true, wantEvery: false, }, { desc: "not multiErrs and non equal", giveErr: myError1, giveTarget: myError2, wantIs: false, wantEvery: false, }, { desc: "not multiErrs but equal", giveErr: myError1, giveTarget: myError1, wantIs: true, wantEvery: true, }, { desc: "not multiErr w multiErr target", giveErr: myError1, giveTarget: newMultiErr(myError1, myError1), wantIs: false, wantEvery: false, }, { desc: "multiErr w multiErr target", giveErr: newMultiErr(myError1, myError1), giveTarget: newMultiErr(myError1, myError1), wantIs: false, wantEvery: false, }, } { t.Run(tt.desc, func(t *testing.T) { assert.Equal(t, tt.wantIs, errors.Is(tt.giveErr, tt.giveTarget)) assert.Equal(t, tt.wantEvery, Every(tt.giveErr, tt.giveTarget)) }) } } func TestCombine(t *testing.T) { tests := []struct { // Input giveErrors []error // Resulting error wantError error // %+v and %v string representations wantMultiline string wantSingleline string }{ { giveErrors: nil, wantError: nil, }, { giveErrors: []error{}, wantError: nil, }, { giveErrors: []error{ errors.New("foo"), nil, newMultiErr( errors.New("bar"), ), nil, }, wantError: newMultiErr( errors.New("foo"), errors.New("bar"), ), wantMultiline: "the following errors occurred:\n" + " - foo\n" + " - bar", wantSingleline: "foo; bar", }, { giveErrors: []error{nil, nil, errors.New("great sadness"), nil}, wantError: errors.New("great sadness"), wantMultiline: "great sadness", wantSingleline: "great sadness", }, { giveErrors: []error{ errors.New("foo"), newMultiErr( errors.New("bar"), ), }, wantError: newMultiErr( errors.New("foo"), errors.New("bar"), ), wantMultiline: "the following errors occurred:\n" + " - foo\n" + " - bar", wantSingleline: "foo; bar", }, { giveErrors: []error{errors.New("great sadness")}, wantError: errors.New("great sadness"), wantMultiline: "great sadness", wantSingleline: "great sadness", }, { giveErrors: []error{ errors.New("foo"), errors.New("bar"), }, wantError: newMultiErr( errors.New("foo"), errors.New("bar"), ), wantMultiline: "the following errors occurred:\n" + " - foo\n" + " - bar", wantSingleline: "foo; bar", }, { giveErrors: []error{ errors.New("great sadness"), errors.New("multi\n line\nerror message"), errors.New("single line error message"), }, wantError: newMultiErr( errors.New("great sadness"), errors.New("multi\n line\nerror message"), errors.New("single line error message"), ), wantMultiline: "the following errors occurred:\n" + " - great sadness\n" + " - multi\n" + " line\n" + " error message\n" + " - single line error message", wantSingleline: "great sadness; " + "multi\n line\nerror message; " + "single line error message", }, { giveErrors: []error{ errors.New("foo"), newMultiErr( errors.New("bar"), errors.New("baz"), ), errors.New("qux"), }, wantError: newMultiErr( errors.New("foo"), errors.New("bar"), errors.New("baz"), errors.New("qux"), ), wantMultiline: "the following errors occurred:\n" + " - foo\n" + " - bar\n" + " - baz\n" + " - qux", wantSingleline: "foo; bar; baz; qux", }, { giveErrors: []error{ errors.New("foo"), nil, newMultiErr( errors.New("bar"), ), nil, }, wantError: newMultiErr( errors.New("foo"), errors.New("bar"), ), wantMultiline: "the following errors occurred:\n" + " - foo\n" + " - bar", wantSingleline: "foo; bar", }, { giveErrors: []error{ errors.New("foo"), newMultiErr( errors.New("bar"), ), }, wantError: newMultiErr( errors.New("foo"), errors.New("bar"), ), wantMultiline: "the following errors occurred:\n" + " - foo\n" + " - bar", wantSingleline: "foo; bar", }, { giveErrors: []error{ errors.New("foo"), richFormatError{}, errors.New("bar"), }, wantError: newMultiErr( errors.New("foo"), richFormatError{}, errors.New("bar"), ), wantMultiline: "the following errors occurred:\n" + " - foo\n" + " - multiline\n" + " message\n" + " with plus\n" + " - bar", wantSingleline: "foo; without plus; bar", }, } for i, tt := range tests { t.Run(fmt.Sprint(i), func(t *testing.T) { err := Combine(tt.giveErrors...) require.Equal(t, tt.wantError, err) if tt.wantMultiline != "" { t.Run("Sprintf/multiline", func(t *testing.T) { assert.Equal(t, tt.wantMultiline, fmt.Sprintf("%+v", err)) }) } if tt.wantSingleline != "" { t.Run("Sprintf/singleline", func(t *testing.T) { assert.Equal(t, tt.wantSingleline, fmt.Sprintf("%v", err)) }) t.Run("Error()", func(t *testing.T) { assert.Equal(t, tt.wantSingleline, err.Error()) }) if s, ok := err.(fmt.Stringer); ok { t.Run("String()", func(t *testing.T) { assert.Equal(t, tt.wantSingleline, s.String()) }) } } }) } } func TestCombineDoesNotModifySlice(t *testing.T) { errors := []error{ errors.New("foo"), nil, errors.New("bar"), } assert.NotNil(t, Combine(errors...)) assert.Len(t, errors, 3) assert.Nil(t, errors[1], 3) } func TestCombineGoodCaseNoAlloc(t *testing.T) { errs := make([]error, 10) allocs := testing.AllocsPerRun(100, func() { Combine(errs...) }) assert.Equal(t, 0.0, allocs) } func TestAppend(t *testing.T) { tests := []struct { left error right error want error }{ { left: nil, right: nil, want: nil, }, { left: nil, right: errors.New("great sadness"), want: errors.New("great sadness"), }, { left: errors.New("great sadness"), right: nil, want: errors.New("great sadness"), }, { left: errors.New("foo"), right: errors.New("bar"), want: newMultiErr( errors.New("foo"), errors.New("bar"), ), }, { left: newMultiErr( errors.New("foo"), errors.New("bar"), ), right: errors.New("baz"), want: newMultiErr( errors.New("foo"), errors.New("bar"), errors.New("baz"), ), }, { left: errors.New("baz"), right: newMultiErr( errors.New("foo"), errors.New("bar"), ), want: newMultiErr( errors.New("baz"), errors.New("foo"), errors.New("bar"), ), }, { left: newMultiErr( errors.New("foo"), ), right: newMultiErr( errors.New("bar"), ), want: newMultiErr( errors.New("foo"), errors.New("bar"), ), }, } for _, tt := range tests { assert.Equal(t, tt.want, Append(tt.left, tt.right)) } } type notMultiErr struct{} var _ errorGroup = notMultiErr{} func (notMultiErr) Error() string { return "great sadness" } func (notMultiErr) Errors() []error { return []error{errors.New("great sadness")} } func TestErrors(t *testing.T) { tests := []struct { give error want []error // Don't attempt to cast to errorGroup or *multiError dontCast bool }{ {dontCast: true}, // nil { give: errors.New("hi"), want: []error{errors.New("hi")}, dontCast: true, }, { // We don't yet support non-multierr errors that do // not implement Unwrap() []error. give: notMultiErr{}, want: []error{notMultiErr{}}, dontCast: true, }, { give: Combine( errors.New("foo"), errors.New("bar"), ), want: []error{ errors.New("foo"), errors.New("bar"), }, }, { give: Append( errors.New("foo"), errors.New("bar"), ), want: []error{ errors.New("foo"), errors.New("bar"), }, }, { give: Append( errors.New("foo"), Combine( errors.New("bar"), ), ), want: []error{ errors.New("foo"), errors.New("bar"), }, }, { give: Combine( errors.New("foo"), Append( errors.New("bar"), errors.New("baz"), ), errors.New("qux"), ), want: []error{ errors.New("foo"), errors.New("bar"), errors.New("baz"), errors.New("qux"), }, }, } for i, tt := range tests { t.Run(fmt.Sprint(i), func(t *testing.T) { t.Run("Errors()", func(t *testing.T) { require.Equal(t, tt.want, Errors(tt.give)) }) if tt.dontCast { return } t.Run("multiError", func(t *testing.T) { require.Equal(t, tt.want, tt.give.(*multiError).Errors()) }) t.Run("errorGroup", func(t *testing.T) { require.Equal(t, tt.want, tt.give.(errorGroup).Errors()) }) }) } } func createMultiErrWithCapacity() error { // Create a multiError that has capacity for more errors so Append will // modify the underlying array that may be shared. return appendN(nil, errors.New("append"), 50) } func TestAppendDoesNotModify(t *testing.T) { initial := createMultiErrWithCapacity() err1 := Append(initial, errors.New("err1")) err2 := Append(initial, errors.New("err2")) // Make sure the error messages match, since we do modify the copyNeeded // atomic, the values cannot be compared. assert.EqualError(t, initial, createMultiErrWithCapacity().Error(), "Initial should not be modified") assert.EqualError(t, err1, Append(createMultiErrWithCapacity(), errors.New("err1")).Error()) assert.EqualError(t, err2, Append(createMultiErrWithCapacity(), errors.New("err2")).Error()) } func TestAppendRace(t *testing.T) { initial := createMultiErrWithCapacity() var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() err := initial for j := 0; j < 10; j++ { err = Append(err, errors.New("err")) } }() } wg.Wait() } func TestErrorsSliceIsImmutable(t *testing.T) { err1 := errors.New("err1") err2 := errors.New("err2") err := Append(err1, err2) gotErrors := Errors(err) require.Equal(t, []error{err1, err2}, gotErrors, "errors must match") gotErrors[0] = nil gotErrors[1] = errors.New("err3") require.Equal(t, []error{err1, err2}, Errors(err), "errors must match after modification") } func TestNilMultierror(t *testing.T) { // For safety, all operations on multiError should be safe even if it is // nil. var err *multiError require.Empty(t, err.Error()) require.Empty(t, err.Errors()) } func TestAppendInto(t *testing.T) { tests := []struct { desc string into *error give error want error }{ { desc: "append into empty", into: new(error), give: errors.New("foo"), want: errors.New("foo"), }, { desc: "append into non-empty, non-multierr", into: errorPtr(errors.New("foo")), give: errors.New("bar"), want: Combine( errors.New("foo"), errors.New("bar"), ), }, { desc: "append into non-empty multierr", into: errorPtr(Combine( errors.New("foo"), errors.New("bar"), )), give: errors.New("baz"), want: Combine( errors.New("foo"), errors.New("bar"), errors.New("baz"), ), }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { assert.True(t, AppendInto(tt.into, tt.give)) assert.Equal(t, tt.want, *tt.into) }) } } func TestAppendInvoke(t *testing.T) { tests := []struct { desc string into *error give Invoker want error }{ { desc: "append into empty", into: new(error), give: Invoke(func() error { return errors.New("foo") }), want: errors.New("foo"), }, { desc: "append into non-empty, non-multierr", into: errorPtr(errors.New("foo")), give: Invoke(func() error { return errors.New("bar") }), want: Combine( errors.New("foo"), errors.New("bar"), ), }, { desc: "append into non-empty multierr", into: errorPtr(Combine( errors.New("foo"), errors.New("bar"), )), give: Invoke(func() error { return errors.New("baz") }), want: Combine( errors.New("foo"), errors.New("bar"), errors.New("baz"), ), }, { desc: "close/empty", into: new(error), give: Close(newCloserMock(t, errors.New("foo"))), want: errors.New("foo"), }, { desc: "close/no fail", into: new(error), give: Close(newCloserMock(t, nil)), want: nil, }, { desc: "close/non-empty", into: errorPtr(errors.New("foo")), give: Close(newCloserMock(t, errors.New("bar"))), want: Combine( errors.New("foo"), errors.New("bar"), ), }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { AppendInvoke(tt.into, tt.give) assert.Equal(t, tt.want, *tt.into) }) } } func TestClose(t *testing.T) { t.Run("fail", func(t *testing.T) { give := errors.New("great sadness") got := Close(newCloserMock(t, give)).Invoke() assert.Same(t, give, got) }) t.Run("success", func(t *testing.T) { got := Close(newCloserMock(t, nil)).Invoke() assert.Nil(t, got) }) } func TestAppendIntoNil(t *testing.T) { t.Run("nil pointer panics", func(t *testing.T) { assert.Panics(t, func() { AppendInto(nil, errors.New("foo")) }) }) t.Run("nil error is no-op", func(t *testing.T) { t.Run("empty left", func(t *testing.T) { var err error assert.False(t, AppendInto(&err, nil)) assert.Nil(t, err) }) t.Run("non-empty left", func(t *testing.T) { err := errors.New("foo") assert.False(t, AppendInto(&err, nil)) assert.Equal(t, errors.New("foo"), err) }) }) } func TestAppendFunc(t *testing.T) { var ( errDeferred = errors.New("deferred func called") errOriginal = errors.New("original error") ) stopFunc := func() error { return errDeferred } err := func() (err error) { defer AppendFunc(&err, stopFunc) return errOriginal }() assert.Equal(t, []error{errOriginal, errDeferred}, Errors(err), "both deferred and original error must be returned") } func errorPtr(err error) *error { return &err } type closerMock func() error func (c closerMock) Close() error { return c() } func newCloserMock(tb testing.TB, err error) io.Closer { var closed bool tb.Cleanup(func() { if !closed { tb.Error("closerMock wasn't closed before test end") } }) return closerMock(func() error { closed = true return err }) } golang-go.uber-multierr-1.11.0/example_test.go000066400000000000000000000056351520242764500213020ustar00rootroot00000000000000// Copyright (c) 2017-2021 Uber Technologies, Inc. // // 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. package multierr_test import ( "errors" "fmt" "io" "go.uber.org/multierr" ) func ExampleCombine() { err := multierr.Combine( errors.New("call 1 failed"), nil, // successful request errors.New("call 3 failed"), nil, // successful request errors.New("call 5 failed"), ) fmt.Printf("%+v", err) // Output: // the following errors occurred: // - call 1 failed // - call 3 failed // - call 5 failed } func ExampleAppend() { var err error err = multierr.Append(err, errors.New("call 1 failed")) err = multierr.Append(err, errors.New("call 2 failed")) fmt.Println(err) // Output: // call 1 failed; call 2 failed } func ExampleErrors() { err := multierr.Combine( nil, // successful request errors.New("call 2 failed"), errors.New("call 3 failed"), ) err = multierr.Append(err, nil) // successful request err = multierr.Append(err, errors.New("call 5 failed")) errors := multierr.Errors(err) for _, err := range errors { fmt.Println(err) } // Output: // call 2 failed // call 3 failed // call 5 failed } func ExampleAppendInto() { var err error if multierr.AppendInto(&err, errors.New("foo")) { fmt.Println("call 1 failed") } if multierr.AppendInto(&err, nil) { fmt.Println("call 2 failed") } if multierr.AppendInto(&err, errors.New("baz")) { fmt.Println("call 3 failed") } fmt.Println(err) // Output: // call 1 failed // call 3 failed // foo; baz } type fakeCloser func() error func (f fakeCloser) Close() error { return f() } func FakeCloser(err error) io.Closer { return fakeCloser(func() error { return err }) } func ExampleClose() { var err error closer := FakeCloser(errors.New("foo")) defer func() { fmt.Println(err) }() defer multierr.AppendInvoke(&err, multierr.Close(closer)) fmt.Println("Hello, World") // Output: // Hello, World // foo } golang-go.uber-multierr-1.11.0/go.mod000066400000000000000000000003441520242764500173570ustar00rootroot00000000000000module go.uber.org/multierr go 1.19 require github.com/stretchr/testify v1.7.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-go.uber-multierr-1.11.0/go.sum000066400000000000000000000022141520242764500174020ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-go.uber-multierr-1.11.0/tools/000077500000000000000000000000001520242764500174105ustar00rootroot00000000000000golang-go.uber-multierr-1.11.0/tools/go.mod000066400000000000000000000006021520242764500205140ustar00rootroot00000000000000module go.uber.org/multierr/tools go 1.18 require ( golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 honnef.co/go/tools v0.4.0 ) require ( github.com/BurntSushi/toml v1.2.1 // indirect golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect golang.org/x/mod v0.7.0 // indirect golang.org/x/sys v0.4.0 // indirect golang.org/x/tools v0.5.0 // indirect ) golang-go.uber-multierr-1.11.0/tools/go.sum000066400000000000000000000046331520242764500205510ustar00rootroot00000000000000github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= honnef.co/go/tools v0.4.0 h1:lyXVV1c8wUBJRKqI8JgIpT8TW1VDagfYYaxbKa/HoL8= honnef.co/go/tools v0.4.0/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= golang-go.uber-multierr-1.11.0/tools/tools.go000066400000000000000000000024211520242764500210760ustar00rootroot00000000000000// Copyright (c) 2019-2021 Uber Technologies, Inc. // // 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. //go:build tools // +build tools package multierr import ( // Tools we use during development. _ "golang.org/x/lint/golint" _ "honnef.co/go/tools/cmd/staticcheck" )