pax_global_header00006660000000000000000000000064141774363170014527gustar00rootroot0000000000000052 comment=932688848b3eb77c80936bf2012620103ab103ce golang-go.uber-multierr-1.6.0/000077500000000000000000000000001417743631700162025ustar00rootroot00000000000000golang-go.uber-multierr-1.6.0/.codecov.yml000066400000000000000000000013731417743631700204310ustar00rootroot00000000000000coverage: 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.6.0/.gitignore000066400000000000000000000000421417743631700201660ustar00rootroot00000000000000/vendor cover.html cover.out /bin golang-go.uber-multierr-1.6.0/.travis.yml000066400000000000000000000004101417743631700203060ustar00rootroot00000000000000sudo: false language: go go_import_path: go.uber.org/multierr env: global: - GO111MODULE=on go: - oldstable - stable before_install: - go version script: - | set -e make lint make cover after_success: - bash <(curl -s https://codecov.io/bash) golang-go.uber-multierr-1.6.0/CHANGELOG.md000066400000000000000000000020621417743631700200130ustar00rootroot00000000000000Releases ======== 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.6.0/LICENSE.txt000066400000000000000000000020531417743631700200250ustar00rootroot00000000000000Copyright (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. golang-go.uber-multierr-1.6.0/Makefile000066400000000000000000000017251417743631700176470ustar00rootroot00000000000000# 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 -coverprofile=cover.out -coverpkg=./... -v ./... go tool cover -html=cover.out -o cover.html update-license: @cd tools && go install go.uber.org/tools/update-license @$(GOBIN)/update-license $(GO_FILES) golang-go.uber-multierr-1.6.0/README.md000066400000000000000000000014031417743631700174570ustar00rootroot00000000000000# 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. ## Installation go get -u go.uber.org/multierr ## Status Stable: No breaking changes will be made before 2.0. ------------------------------------------------------------------------------- Released under the [MIT License]. [MIT License]: LICENSE.txt [doc-img]: https://godoc.org/go.uber.org/multierr?status.svg [doc]: https://godoc.org/go.uber.org/multierr [ci-img]: https://travis-ci.com/uber-go/multierr.svg?branch=master [cov-img]: https://codecov.io/gh/uber-go/multierr/branch/master/graph/badge.svg [ci]: https://travis-ci.com/uber-go/multierr [cov]: https://codecov.io/gh/uber-go/multierr golang-go.uber-multierr-1.6.0/benchmarks_test.go000066400000000000000000000034651417743631700217150ustar00rootroot00000000000000// 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) } }) } }) } } } golang-go.uber-multierr-1.6.0/error.go000066400000000000000000000266141417743631700176730ustar00rootroot00000000000000// Copyright (c) 2019 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()) // // This makes it possible to record resource cleanup failures from deferred // blocks with the help of named return values. // // func sendRequest(req Request) (err error) { // conn, err := openConnection() // if err != nil { // return err // } // defer func() { // err = multierr.Append(err, conn.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) // } // // 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" "go.uber.org/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} } errors := eg.Errors() result := make([]error, len(errors)) copy(result, errors) return result } // 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 { 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 { // already flat return &multiError{errors: errors} } } 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()) // }() 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 verison 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 } golang-go.uber-multierr-1.6.0/error_test.go000066400000000000000000000303061417743631700207230ustar00rootroot00000000000000// Copyright (c) 2019 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{ 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 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 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 errorPtr(err error) *error { return &err } golang-go.uber-multierr-1.6.0/example_test.go000066400000000000000000000047521417743631700212330ustar00rootroot00000000000000// 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_test import ( "errors" "fmt" "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 } golang-go.uber-multierr-1.6.0/glide.yaml000066400000000000000000000002371417743631700201540ustar00rootroot00000000000000package: go.uber.org/multierr import: - package: go.uber.org/atomic version: ^1 testImport: - package: github.com/stretchr/testify subpackages: - assert golang-go.uber-multierr-1.6.0/go.mod000066400000000000000000000001611417743631700173060ustar00rootroot00000000000000module go.uber.org/multierr go 1.12 require ( github.com/stretchr/testify v1.3.0 go.uber.org/atomic v1.7.0 ) golang-go.uber-multierr-1.6.0/go.sum000066400000000000000000000016461417743631700173440ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang-go.uber-multierr-1.6.0/go113.go000066400000000000000000000035151417743631700173670ustar00rootroot00000000000000// Copyright (c) 2019 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. // +build go1.13 package multierr import "errors" // 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 } golang-go.uber-multierr-1.6.0/go113_test.go000066400000000000000000000074731417743631700204350ustar00rootroot00000000000000// Copyright (c) 2019 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. // +build go1.13 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.6.0/tools/000077500000000000000000000000001417743631700173425ustar00rootroot00000000000000golang-go.uber-multierr-1.6.0/tools/go.mod000066400000000000000000000004131417743631700204460ustar00rootroot00000000000000module go.uber.org/multierr/tools go 1.12 require ( go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee golang.org/x/lint v0.0.0-20190930215403-16217165b5de golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 // indirect honnef.co/go/tools v0.0.1-2019.2.3 ) golang-go.uber-multierr-1.6.0/tools/go.sum000066400000000000000000000061231417743631700204770ustar00rootroot00000000000000github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= 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/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= golang-go.uber-multierr-1.6.0/tools/tools.go000066400000000000000000000024411417743631700210320ustar00rootroot00000000000000// Copyright (c) 2019 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. // +build tools package multierr import ( // Tools we use during development. _ "go.uber.org/tools/update-license" _ "golang.org/x/lint/golint" _ "honnef.co/go/tools/cmd/staticcheck" )