From bc4e624732bd51c0dd1e9529cf228e8c23127732 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 13:40:59 +0200 Subject: Adding upstream version 1.1.1. Signed-off-by: Daniel Baumann --- .../mod/go.uber.org/multierr@v1.10.0/.codecov.yml | 15 + .../multierr@v1.10.0/.github/workflows/fossa.yaml | 17 + .../multierr@v1.10.0/.github/workflows/go.yml | 49 ++ .../mod/go.uber.org/multierr@v1.10.0/.gitignore | 4 + .../mod/go.uber.org/multierr@v1.10.0/CHANGELOG.md | 88 +++ .../mod/go.uber.org/multierr@v1.10.0/LICENSE.txt | 19 + .../pkg/mod/go.uber.org/multierr@v1.10.0/Makefile | 38 ++ .../pkg/mod/go.uber.org/multierr@v1.10.0/README.md | 43 ++ .../multierr@v1.10.0/appendinvoke_example_test.go | 73 +++ .../multierr@v1.10.0/benchmarks_test.go | 127 ++++ .../pkg/mod/go.uber.org/multierr@v1.10.0/error.go | 652 +++++++++++++++++++ .../go.uber.org/multierr@v1.10.0/error_ext_test.go | 142 ++++ .../multierr@v1.10.0/error_post_go120.go | 29 + .../multierr@v1.10.0/error_pre_go120.go | 59 ++ .../mod/go.uber.org/multierr@v1.10.0/error_test.go | 717 +++++++++++++++++++++ .../go.uber.org/multierr@v1.10.0/example_test.go | 124 ++++ .../pkg/mod/go.uber.org/multierr@v1.10.0/go.mod | 11 + .../pkg/mod/go.uber.org/multierr@v1.10.0/go.sum | 13 + 18 files changed, 2220 insertions(+) create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.codecov.yml create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.github/workflows/fossa.yaml create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.github/workflows/go.yml create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.gitignore create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/CHANGELOG.md create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/LICENSE.txt create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/Makefile create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/README.md create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/appendinvoke_example_test.go create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/benchmarks_test.go create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error.go create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_ext_test.go create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_post_go120.go create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_pre_go120.go create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_test.go create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/example_test.go create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/go.mod create mode 100644 dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/go.sum (limited to 'dependencies/pkg/mod/go.uber.org/multierr@v1.10.0') diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.codecov.yml b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.codecov.yml new file mode 100644 index 0000000..6d4d1be --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.codecov.yml @@ -0,0 +1,15 @@ +coverage: + 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 + diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.github/workflows/fossa.yaml b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.github/workflows/fossa.yaml new file mode 100644 index 0000000..86e6db7 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.github/workflows/fossa.yaml @@ -0,0 +1,17 @@ +name: FOSSA Analysis +on: push + +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 }} + diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.github/workflows/go.yml b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.github/workflows/go.yml new file mode 100644 index 0000000..23c78ab --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.github/workflows/go.yml @@ -0,0 +1,49 @@ +name: Go + +on: + push: + branches: ['*'] + tags: ['v*'] + pull_request: + branches: ['*'] + +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 diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.gitignore b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.gitignore new file mode 100644 index 0000000..b9a05e3 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/.gitignore @@ -0,0 +1,4 @@ +/vendor +cover.html +cover.out +/bin diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/CHANGELOG.md b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/CHANGELOG.md new file mode 100644 index 0000000..cfd2e6a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/CHANGELOG.md @@ -0,0 +1,88 @@ +Releases +======== + +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 diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/LICENSE.txt b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/LICENSE.txt new file mode 100644 index 0000000..413e30f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/LICENSE.txt @@ -0,0 +1,19 @@ +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. diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/Makefile b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/Makefile new file mode 100644 index 0000000..dcb6fe7 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/Makefile @@ -0,0 +1,38 @@ +# 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 diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/README.md b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/README.md new file mode 100644 index 0000000..5ab6ac4 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/README.md @@ -0,0 +1,43 @@ +# 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 diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/appendinvoke_example_test.go b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/appendinvoke_example_test.go new file mode 100644 index 0000000..f8b674b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/appendinvoke_example_test.go @@ -0,0 +1,73 @@ +// 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)) +} diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/benchmarks_test.go b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/benchmarks_test.go new file mode 100644 index 0000000..562d3bc --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/benchmarks_test.go @@ -0,0 +1,127 @@ +// 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...) + } + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error.go b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error.go new file mode 100644 index 0000000..4ee4b9f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error.go @@ -0,0 +1,652 @@ +// 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" + "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 { + 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()...) +} + +// 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 +} + +var _ errorGroup = (*multiError)(nil) + +// 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 +} + +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)) +} diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_ext_test.go b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_ext_test.go new file mode 100644 index 0000000..9936e36 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_ext_test.go @@ -0,0 +1,142 @@ +// 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})) + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_post_go120.go b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_post_go120.go new file mode 100644 index 0000000..0b00bec --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_post_go120.go @@ -0,0 +1,29 @@ +// 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() +} diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_pre_go120.go b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_pre_go120.go new file mode 100644 index 0000000..8da10f1 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_pre_go120.go @@ -0,0 +1,59 @@ +// 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 +} diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_test.go b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_test.go new file mode 100644 index 0000000..39ca313 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/error_test.go @@ -0,0 +1,717 @@ +// 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 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. + 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 + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/example_test.go b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/example_test.go new file mode 100644 index 0000000..e46a633 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/example_test.go @@ -0,0 +1,124 @@ +// 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 +} diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/go.mod b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/go.mod new file mode 100644 index 0000000..1b4e0f5 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/go.mod @@ -0,0 +1,11 @@ +module 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 +) diff --git a/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/go.sum b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/go.sum new file mode 100644 index 0000000..e43413c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/multierr@v1.10.0/go.sum @@ -0,0 +1,13 @@ +github.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= -- cgit v1.2.3