├── go.mod ├── .travis.yml ├── errdoc-gen ├── README.md └── main.go ├── .gitignore ├── group.go ├── LICENSE ├── normalize_test.go ├── join.go ├── join_test.go ├── bench_test.go ├── README.md ├── go.sum ├── compatible_shim.go ├── juju_adaptor.go ├── terror_test └── terror_test.go ├── example_test.go ├── stack.go ├── stack_test.go ├── normalize.go ├── errors.go ├── errors_test.go └── format_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pingcap/errors 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/stretchr/testify v1.4.0 7 | go.uber.org/atomic v1.6.0 8 | ) 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | arch: 2 | - amd64 3 | 4 | language: go 5 | go_import_path: github.com/pingcap/errors 6 | go: 7 | - 1.13.x 8 | - stable 9 | 10 | script: 11 | - go test -v ./... 12 | -------------------------------------------------------------------------------- /errdoc-gen/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | ```shell script 4 | # eg: ./errdoc-gen --source devel/pingap/tidb --module github.com/pingcap/tidb --output devel/pingap/tidb/errors.toml 5 | ./errdoc-gen --source /path/to/source/code --module ${module-name} --output /path/to/errors.toml 6 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | .idea 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | -------------------------------------------------------------------------------- /group.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | // ErrorGroup is an interface for multiple errors that are not a chain. 4 | // This happens for example when executing multiple operations in parallel. 5 | type ErrorGroup interface { 6 | Errors() []error 7 | } 8 | 9 | // Errors uses the ErrorGroup interface to return a slice of errors. 10 | // If the ErrorGroup interface is not implemented it returns an array containing just the given error. 11 | func Errors(err error) []error { 12 | if eg, ok := err.(ErrorGroup); ok { 13 | return eg.Errors() 14 | } 15 | return []error{err} 16 | } 17 | 18 | // WalkDeep does a depth-first traversal of all errors. 19 | // Any ErrorGroup is traversed (after going deep). 20 | // The visitor function can return true to end the traversal early 21 | // In that case, WalkDeep will return true, otherwise false. 22 | func WalkDeep(err error, visitor func(err error) bool) bool { 23 | if err == nil { 24 | return false 25 | } 26 | 27 | if visitor(err) { 28 | return true 29 | } 30 | 31 | // Go deep 32 | unErr := Unwrap(err) 33 | if unErr != nil { 34 | if WalkDeep(unErr, visitor) { 35 | return true 36 | } 37 | } 38 | 39 | // Go wide 40 | if group, ok := err.(ErrorGroup); ok { 41 | for _, err := range group.Errors() { 42 | if early := WalkDeep(err, visitor); early { 43 | return true 44 | } 45 | } 46 | } 47 | 48 | return false 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Dave Cheney 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /normalize_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "testing" 7 | ) 8 | 9 | func errorMatches(t *testing.T, err error, re string) { 10 | if err == nil && re != "" { 11 | t.Errorf("nil error doesn't match %s", re) 12 | return 13 | } 14 | match, reErr := regexp.MatchString(re, err.Error()) 15 | if reErr != nil { 16 | t.Errorf("invalid regexp %s (%s)", re, reErr.Error()) 17 | return 18 | } 19 | if !match { 20 | t.Errorf("error %s doesn't match %s", err.Error(), re) 21 | return 22 | } 23 | t.Logf("passed: %s ~= %s", err.Error(), re) 24 | } 25 | 26 | func TestCauseInErrorMessage(t *testing.T) { 27 | errTest := Normalize("this error just for testing", RFCCodeText("Internal:Test")) 28 | 29 | wrapped := errTest.Wrap(New("everything is alright :)")) 30 | errorMatches(t, wrapped, `\[Internal:Test\]this error just for testing: everything is alright :\)`) 31 | 32 | notWrapped := errTest.GenWithStack("everything is alright") 33 | errorMatches(t, notWrapped, `^\[Internal:Test\]everything is alright$`) 34 | } 35 | 36 | func TestRedactFormatter(t *testing.T) { 37 | rv := 34.03498 38 | v := &redactFormatter{rv} 39 | for _, f := range []string{"%d", "%.2d"} { 40 | a := fmt.Sprintf(f, v) 41 | b := fmt.Sprintf("‹"+f+"›", rv) 42 | if a != b { 43 | t.Errorf("%s != %s", a, b) 44 | } 45 | } 46 | 47 | v = &redactFormatter{"‹"} 48 | if a := fmt.Sprintf("%s", v); a != "‹‹‹›" { 49 | t.Errorf("%s != <<<>", a) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /join.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package errors 15 | 16 | // Join returns an error that wraps the given errors. 17 | // Any nil error values are discarded. 18 | // Join returns nil if every value in errs is nil. 19 | // The error formats as the concatenation of the strings obtained 20 | // by calling the Error method of each element of errs, with a newline 21 | // between each string. 22 | // 23 | // A non-nil error returned by Join implements the Unwrap() []error method. 24 | func Join(errs ...error) error { 25 | n := 0 26 | for _, err := range errs { 27 | if err != nil { 28 | n++ 29 | } 30 | } 31 | if n == 0 { 32 | return nil 33 | } 34 | e := &joinError{ 35 | errs: make([]error, 0, n), 36 | } 37 | for _, err := range errs { 38 | if err != nil { 39 | e.errs = append(e.errs, err) 40 | } 41 | } 42 | return e 43 | } 44 | 45 | type joinError struct { 46 | errs []error 47 | } 48 | 49 | func (e *joinError) Error() string { 50 | var b []byte 51 | for i, err := range e.errs { 52 | if i > 0 { 53 | b = append(b, '\n') 54 | } 55 | b = append(b, err.Error()...) 56 | } 57 | return string(b) 58 | } 59 | 60 | func (e *joinError) Unwrap() []error { 61 | return e.errs 62 | } 63 | -------------------------------------------------------------------------------- /join_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package errors 15 | 16 | import ( 17 | "reflect" 18 | "testing" 19 | ) 20 | 21 | func TestJoinReturnsNil(t *testing.T) { 22 | if err := Join(); err != nil { 23 | t.Errorf("Join() = %v, want nil", err) 24 | } 25 | if err := Join(nil); err != nil { 26 | t.Errorf("Join(nil) = %v, want nil", err) 27 | } 28 | if err := Join(nil, nil); err != nil { 29 | t.Errorf("Join(nil, nil) = %v, want nil", err) 30 | } 31 | } 32 | 33 | func TestJoin(t *testing.T) { 34 | err1 := New("err1") 35 | err2 := New("err2") 36 | for _, test := range []struct { 37 | errs []error 38 | want []error 39 | }{{ 40 | errs: []error{err1}, 41 | want: []error{err1}, 42 | }, { 43 | errs: []error{err1, err2}, 44 | want: []error{err1, err2}, 45 | }, { 46 | errs: []error{err1, nil, err2}, 47 | want: []error{err1, err2}, 48 | }} { 49 | got := Join(test.errs...).(interface{ Unwrap() []error }).Unwrap() 50 | if !reflect.DeepEqual(got, test.want) { 51 | t.Errorf("Join(%v) = %v; want %v", test.errs, got, test.want) 52 | } 53 | if len(got) != cap(got) { 54 | t.Errorf("Join(%v) returns errors with len=%v, cap=%v; want len==cap", test.errs, len(got), cap(got)) 55 | } 56 | } 57 | } 58 | 59 | func TestJoinErrorMethod(t *testing.T) { 60 | err1 := New("err1") 61 | err2 := New("err2") 62 | for _, test := range []struct { 63 | errs []error 64 | want string 65 | }{{ 66 | errs: []error{err1}, 67 | want: "err1", 68 | }, { 69 | errs: []error{err1, err2}, 70 | want: "err1\nerr2", 71 | }, { 72 | errs: []error{err1, nil, err2}, 73 | want: "err1\nerr2", 74 | }} { 75 | got := Join(test.errs...).Error() 76 | if got != test.want { 77 | t.Errorf("Join(%v).Error() = %q; want %q", test.errs, got, test.want) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | stderrors "errors" 8 | ) 9 | 10 | func noErrors(at, depth int) error { 11 | if at >= depth { 12 | return stderrors.New("no error") 13 | } 14 | return noErrors(at+1, depth) 15 | } 16 | 17 | func yesErrors(at, depth int) error { 18 | if at >= depth { 19 | return New("ye error") 20 | } 21 | return yesErrors(at+1, depth) 22 | } 23 | 24 | // GlobalE is an exported global to store the result of benchmark results, 25 | // preventing the compiler from optimising the benchmark functions away. 26 | var GlobalE interface{} 27 | 28 | func BenchmarkErrors(b *testing.B) { 29 | type run struct { 30 | stack int 31 | std bool 32 | } 33 | runs := []run{ 34 | {10, false}, 35 | {10, true}, 36 | {100, false}, 37 | {100, true}, 38 | {1000, false}, 39 | {1000, true}, 40 | } 41 | for _, r := range runs { 42 | part := "pkg/errors" 43 | if r.std { 44 | part = "errors" 45 | } 46 | name := fmt.Sprintf("%s-stack-%d", part, r.stack) 47 | b.Run(name, func(b *testing.B) { 48 | var err error 49 | f := yesErrors 50 | if r.std { 51 | f = noErrors 52 | } 53 | b.ReportAllocs() 54 | for i := 0; i < b.N; i++ { 55 | err = f(0, r.stack) 56 | } 57 | b.StopTimer() 58 | GlobalE = err 59 | }) 60 | } 61 | } 62 | 63 | func BenchmarkStackFormatting(b *testing.B) { 64 | type run struct { 65 | stack int 66 | format string 67 | } 68 | runs := []run{ 69 | {10, "%s"}, 70 | {10, "%v"}, 71 | {10, "%+v"}, 72 | {30, "%s"}, 73 | {30, "%v"}, 74 | {30, "%+v"}, 75 | {60, "%s"}, 76 | {60, "%v"}, 77 | {60, "%+v"}, 78 | } 79 | 80 | var stackStr string 81 | for _, r := range runs { 82 | name := fmt.Sprintf("%s-stack-%d", r.format, r.stack) 83 | b.Run(name, func(b *testing.B) { 84 | err := yesErrors(0, r.stack) 85 | b.ReportAllocs() 86 | b.ResetTimer() 87 | for i := 0; i < b.N; i++ { 88 | stackStr = fmt.Sprintf(r.format, err) 89 | } 90 | b.StopTimer() 91 | }) 92 | } 93 | 94 | for _, r := range runs { 95 | name := fmt.Sprintf("%s-stacktrace-%d", r.format, r.stack) 96 | b.Run(name, func(b *testing.B) { 97 | err := yesErrors(0, r.stack) 98 | st := err.(*fundamental).stack.StackTrace() 99 | b.ReportAllocs() 100 | b.ResetTimer() 101 | for i := 0; i < b.N; i++ { 102 | stackStr = fmt.Sprintf(r.format, st) 103 | } 104 | b.StopTimer() 105 | }) 106 | } 107 | GlobalE = stackStr 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) 2 | 3 | Package errors provides simple error handling primitives. 4 | 5 | `go get github.com/pingcap/errors` 6 | 7 | The traditional error handling idiom in Go is roughly akin to 8 | ```go 9 | if err != nil { 10 | return err 11 | } 12 | ``` 13 | which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. 14 | 15 | ## Adding context to an error 16 | 17 | The errors.Wrap function returns a new error that adds context to the original error. For example 18 | ```go 19 | _, err := ioutil.ReadAll(r) 20 | if err != nil { 21 | return errors.Wrap(err, "read failed") 22 | } 23 | ``` 24 | ## Retrieving the cause of an error 25 | 26 | Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. 27 | ```go 28 | type causer interface { 29 | Cause() error 30 | } 31 | ``` 32 | `errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: 33 | ```go 34 | switch err := errors.Cause(err).(type) { 35 | case *MyError: 36 | // handle specifically 37 | default: 38 | // unknown error 39 | } 40 | ``` 41 | 42 | [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). 43 | 44 | ## Contributing 45 | 46 | We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. 47 | 48 | Before proposing a change, please discuss your change by raising an issue. 49 | 50 | ## License 51 | 52 | BSD-2-Clause 53 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 8 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 9 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 10 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= 11 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 12 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 13 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 14 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 15 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 16 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 17 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 18 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 19 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 20 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 21 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c h1:IGkKhmfzcztjm6gYkykvu/NiS8kaqbCWAEWWAyf8J5U= 22 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 23 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 24 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 26 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 27 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 28 | -------------------------------------------------------------------------------- /compatible_shim.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package errors 15 | 16 | import ( 17 | "encoding/json" 18 | "strconv" 19 | "strings" 20 | ) 21 | 22 | // class2RFCCode is used for compatible with old version of TiDB. When 23 | // marshal Error to json, old version of TiDB contain a 'class' field 24 | // which is represented for error class. In order to parse and convert 25 | // json to errors.Error, using this map to convert error class to RFC 26 | // error code text. here is reference: 27 | // https://github.com/pingcap/parser/blob/release-3.0/terror/terror.go#L58 28 | var class2RFCCode = map[int]string{ 29 | 1: "autoid", 30 | 2: "ddl", 31 | 3: "domain", 32 | 4: "evaluator", 33 | 5: "executor", 34 | 6: "expression", 35 | 7: "admin", 36 | 8: "kv", 37 | 9: "meta", 38 | 10: "planner", 39 | 11: "parser", 40 | 12: "perfschema", 41 | 13: "privilege", 42 | 14: "schema", 43 | 15: "server", 44 | 16: "struct", 45 | 17: "variable", 46 | 18: "xeval", 47 | 19: "table", 48 | 20: "types", 49 | 21: "global", 50 | 22: "mocktikv", 51 | 23: "json", 52 | 24: "tikv", 53 | 25: "session", 54 | 26: "plugin", 55 | 27: "util", 56 | } 57 | var rfcCode2class map[string]int 58 | 59 | func init() { 60 | rfcCode2class = make(map[string]int) 61 | for k, v := range class2RFCCode { 62 | rfcCode2class[v] = k 63 | } 64 | } 65 | 66 | // MarshalJSON implements json.Marshaler interface. 67 | // aware that this function cannot save a 'registered' status, 68 | // since we cannot access the registry when unmarshaling, 69 | // and the original global registry would be removed here. 70 | // This function is reserved for compatibility. 71 | func (e *Error) MarshalJSON() ([]byte, error) { 72 | ec := strings.Split(string(e.codeText), ":")[0] 73 | return json.Marshal(&jsonError{ 74 | Class: rfcCode2class[ec], 75 | Code: int(e.code), 76 | Msg: e.GetMsg(), 77 | RFCCode: string(e.codeText), 78 | }) 79 | } 80 | 81 | // UnmarshalJSON implements json.Unmarshaler interface. 82 | // aware that this function cannot create a 'registered' error, 83 | // since we cannot access the registry in this context, 84 | // and the original global registry is removed. 85 | // This function is reserved for compatibility. 86 | func (e *Error) UnmarshalJSON(data []byte) error { 87 | tErr := &jsonError{} 88 | if err := json.Unmarshal(data, &tErr); err != nil { 89 | return Trace(err) 90 | } 91 | e.codeText = ErrCodeText(tErr.RFCCode) 92 | if tErr.RFCCode == "" && tErr.Class > 0 { 93 | e.codeText = ErrCodeText(class2RFCCode[tErr.Class] + ":" + strconv.Itoa(tErr.Code)) 94 | } 95 | e.code = ErrCode(tErr.Code) 96 | e.message = tErr.Msg 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /juju_adaptor.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // ==================== juju adaptor start ======================== 9 | 10 | // Trace just calls AddStack. 11 | func Trace(err error) error { 12 | if err == nil { 13 | return nil 14 | } 15 | return AddStack(err) 16 | } 17 | 18 | // Annotate adds a message and ensures there is a stack trace. 19 | func Annotate(err error, message string) error { 20 | if err == nil { 21 | return nil 22 | } 23 | hasStack := HasStack(err) 24 | err = &withMessage{ 25 | cause: err, 26 | msg: message, 27 | causeHasStack: hasStack, 28 | } 29 | if hasStack { 30 | return err 31 | } 32 | return &withStack{ 33 | err, 34 | callers(), 35 | } 36 | } 37 | 38 | // Annotatef adds a message and ensures there is a stack trace. 39 | func Annotatef(err error, format string, args ...interface{}) error { 40 | if err == nil { 41 | return nil 42 | } 43 | hasStack := HasStack(err) 44 | err = &withMessage{ 45 | cause: err, 46 | msg: fmt.Sprintf(format, args...), 47 | causeHasStack: hasStack, 48 | } 49 | if hasStack { 50 | return err 51 | } 52 | return &withStack{ 53 | err, 54 | callers(), 55 | } 56 | } 57 | 58 | var emptyStack stack 59 | 60 | // NewNoStackError creates error without error stack 61 | // later duplicate trace will no longer generate Stack too. 62 | func NewNoStackError(msg string) error { 63 | return &fundamental{ 64 | msg: msg, 65 | stack: &emptyStack, 66 | } 67 | } 68 | 69 | // NewNoStackErrorf creates error with error stack and formats according 70 | // to a format specifier and returns the string as a value that satisfies error. 71 | func NewNoStackErrorf(format string, args ...interface{}) error { 72 | return &fundamental{ 73 | msg: fmt.Sprintf(format, args...), 74 | stack: &emptyStack, 75 | } 76 | } 77 | 78 | // SuspendStack suspends stack generate for error. 79 | // Deprecated, it's semantic is to clear the stack inside, we still allow upper 80 | // layer to add stack again by using Trace. 81 | // Sometimes we have very deep calling stack, the lower layer calls SuspendStack, 82 | // but the upper layer want to add stack to it, if we disable adding stack permanently 83 | // for an error, it's very hard to diagnose certain issues. 84 | func SuspendStack(err error) error { 85 | if err == nil { 86 | return err 87 | } 88 | cleared := clearStack(err) 89 | if cleared { 90 | return err 91 | } 92 | return &withStack{ 93 | err, 94 | &emptyStack, 95 | } 96 | } 97 | 98 | func clearStack(err error) (cleared bool) { 99 | switch typedErr := err.(type) { 100 | case *withMessage: 101 | return clearStack(typedErr.Cause()) 102 | case *fundamental: 103 | typedErr.stack = &emptyStack 104 | return true 105 | case *withStack: 106 | typedErr.stack = &emptyStack 107 | clearStack(typedErr.Cause()) 108 | return true 109 | default: 110 | return false 111 | } 112 | } 113 | 114 | // ErrorStack will format a stack trace if it is available, otherwise it will be Error() 115 | // If the error is nil, the empty string is returned 116 | // Note that this just calls fmt.Sprintf("%+v", err) 117 | func ErrorStack(err error) string { 118 | if err == nil { 119 | return "" 120 | } 121 | return fmt.Sprintf("%+v", err) 122 | } 123 | 124 | // IsNotFound reports whether err was not found error. 125 | func IsNotFound(err error) bool { 126 | return strings.Contains(err.Error(), "not found") 127 | } 128 | 129 | // NotFoundf represents an error with not found message. 130 | func NotFoundf(format string, args ...interface{}) error { 131 | return Errorf(format+" not found", args...) 132 | } 133 | 134 | // BadRequestf represents an error with bad request message. 135 | func BadRequestf(format string, args ...interface{}) error { 136 | return Errorf(format+" bad request", args...) 137 | } 138 | 139 | // NotSupportedf represents an error with not supported message. 140 | func NotSupportedf(format string, args ...interface{}) error { 141 | return Errorf(format+" not supported", args...) 142 | } 143 | 144 | // NotValidf represents an error with not valid message. 145 | func NotValidf(format string, args ...interface{}) error { 146 | return Errorf(format+" not valid", args...) 147 | } 148 | 149 | // IsAlreadyExists reports whether err was already exists error. 150 | func IsAlreadyExists(err error) bool { 151 | return strings.Contains(err.Error(), "already exists") 152 | } 153 | 154 | // AlreadyExistsf represents an error with already exists message. 155 | func AlreadyExistsf(format string, args ...interface{}) error { 156 | return Errorf(format+" already exists", args...) 157 | } 158 | 159 | // ==================== juju adaptor end ======================== 160 | -------------------------------------------------------------------------------- /terror_test/terror_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package terror_test 15 | 16 | import ( 17 | "encoding/json" 18 | "os" 19 | "runtime" 20 | "strings" 21 | "testing" 22 | "time" 23 | 24 | "github.com/stretchr/testify/suite" 25 | 26 | "github.com/pingcap/errors" 27 | ) 28 | 29 | const ( 30 | CodeMissConnectionID errors.ErrCode = 1 31 | CodeResultUndetermined errors.ErrCode = 2 32 | CodeExecResultIsEmpty errors.ErrCode = 3 33 | ) 34 | 35 | type TErrorTestSuite struct { 36 | suite.Suite 37 | } 38 | 39 | func (s *TErrorTestSuite) TestErrCode() { 40 | s.Equal(CodeMissConnectionID, errors.ErrCode(1)) 41 | s.Equal(CodeResultUndetermined, errors.ErrCode(2)) 42 | s.Equal(CodeExecResultIsEmpty, errors.ErrCode(3)) 43 | } 44 | 45 | var predefinedErr = errors.Normalize("predefiend error", errors.MySQLErrorCode(123)) 46 | var predefinedTextualErr = errors.Normalize("executor is taking vacation at %s", errors.RFCCodeText("executor:ExecutorAbsent")) 47 | 48 | func example() error { 49 | err := call() 50 | return errors.Trace(err) 51 | } 52 | 53 | func call() error { 54 | return predefinedErr.GenWithStack("error message:%s", "abc") 55 | } 56 | 57 | func (s *TErrorTestSuite) TestJson() { 58 | tmpErr := errors.Normalize("this is a test error", errors.RFCCodeText("ddl:-1"), errors.MySQLErrorCode(-1)) 59 | buf, err := json.Marshal(tmpErr) 60 | s.Nil(err) 61 | var curTErr errors.Error 62 | err = json.Unmarshal(buf, &curTErr) 63 | s.Nil(err) 64 | isEqual := tmpErr.Equal(&curTErr) 65 | s.Equal(curTErr.Error(), tmpErr.Error()) 66 | s.True(isEqual) 67 | } 68 | 69 | func (s *TErrorTestSuite) TestTraceAndLocation() { 70 | err := example() 71 | stack := errors.ErrorStack(err) 72 | lines := strings.Split(stack, "\n") 73 | goroot := strings.ReplaceAll(runtime.GOROOT(), string(os.PathSeparator), "/") 74 | var sysStack = 0 75 | for _, line := range lines { 76 | if strings.Contains(line, goroot) { 77 | sysStack++ 78 | } 79 | } 80 | s.Equalf(11, len(lines)-(2*sysStack), "stack = \n%s", stack) 81 | var containTerr bool 82 | for _, v := range lines { 83 | if strings.Contains(v, "terror_test.go") { 84 | containTerr = true 85 | break 86 | } 87 | } 88 | s.True(containTerr) 89 | } 90 | 91 | func (s *TErrorTestSuite) TestErrorEqual() { 92 | e1 := errors.New("test error") 93 | s.NotNil(e1) 94 | 95 | e2 := errors.Trace(e1) 96 | s.NotNil(e2) 97 | 98 | e3 := errors.Trace(e2) 99 | s.NotNil(e3) 100 | 101 | s.Equal(e1, errors.Cause(e2)) 102 | s.Equal(e1, errors.Cause(e3)) 103 | s.Equal(errors.Cause(e3), errors.Cause(e2)) 104 | 105 | e4 := errors.New("test error") 106 | s.NotEqual(e1, errors.Cause(e4)) 107 | 108 | e5 := errors.Errorf("test error") 109 | s.NotEqual(e1, errors.Cause(e5)) 110 | 111 | s.True(errors.ErrorEqual(e1, e2)) 112 | s.True(errors.ErrorEqual(e1, e3)) 113 | s.True(errors.ErrorEqual(e1, e4)) 114 | s.True(errors.ErrorEqual(e1, e5)) 115 | 116 | var e6 error 117 | 118 | s.True(errors.ErrorEqual(nil, nil)) 119 | s.True(errors.ErrorNotEqual(e1, e6)) 120 | } 121 | 122 | func (s *TErrorTestSuite) TestNewError() { 123 | today := time.Now().Weekday().String() 124 | err := predefinedTextualErr.GenWithStackByArgs(today) 125 | s.NotNil(err) 126 | s.Equal("[executor:ExecutorAbsent]executor is taking vacation at "+today, err.Error()) 127 | } 128 | 129 | func (s *TErrorTestSuite) TestRFCCode() { 130 | c1err1 := errors.Normalize("nothing", errors.RFCCodeText("TestErr1:Err1")) 131 | c2err2 := errors.Normalize("nothing", errors.RFCCodeText("TestErr2:Err2")) 132 | s.Equal(errors.RFCErrorCode("TestErr1:Err1"), c1err1.RFCCode()) 133 | s.Equal(errors.RFCErrorCode("TestErr2:Err2"), c2err2.RFCCode()) 134 | 135 | berr := errors.Normalize("nothing", errors.RFCCodeText("Blank:B1")) 136 | s.Equal(errors.RFCErrorCode("Blank:B1"), berr.RFCCode()) 137 | } 138 | 139 | func (s *TErrorTestSuite) TestLineAndFile() { 140 | err := predefinedTextualErr.GenWithStackByArgs("everyday") 141 | _, f, l, _ := runtime.Caller(0) 142 | terr, ok := errors.Cause(err).(*errors.Error) 143 | s.True(ok) 144 | 145 | file, line := terr.Location() 146 | s.Equal(f, file) 147 | s.Equal(l-1, line) 148 | 149 | err2 := predefinedTextualErr.GenWithStackByArgs("everyday and everywhere") 150 | _, f2, l2, _ := runtime.Caller(0) 151 | terr2, ok2 := errors.Cause(err2).(*errors.Error) 152 | s.True(ok2) 153 | file2, line2 := terr2.Location() 154 | s.Equal(f2, file2) 155 | s.Equal(l2-1, line2) 156 | } 157 | 158 | func (s *TErrorTestSuite) TestWarpAndField() { 159 | cause := errors.New("load from etcd meet error") 160 | s.NotNil(cause) 161 | 162 | err := errors.Normalize("fail to get leader", errors.RFCCodeText("member:ErrGetLeader")) 163 | errWithCause := errors.Annotate(err, cause.Error()) 164 | s.NotNil(errWithCause) 165 | 166 | s.Equal("load from etcd meet error: [member:ErrGetLeader]fail to get leader", errWithCause.Error()) 167 | } 168 | 169 | func TestExampleTestSuite(t *testing.T) { 170 | suite.Run(t, new(TErrorTestSuite)) 171 | } 172 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package errors_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/pingcap/errors" 6 | ) 7 | 8 | func ExampleNew() { 9 | err := errors.New("whoops") 10 | fmt.Println(err) 11 | 12 | // Output: whoops 13 | } 14 | 15 | func ExampleNew_printf() { 16 | err := errors.New("whoops") 17 | fmt.Printf("%+v", err) 18 | 19 | // Example output: 20 | // whoops 21 | // github.com/pkg/errors_test.ExampleNew_printf 22 | // /home/dfc/src/github.com/pkg/errors/example_test.go:17 23 | // testing.runExample 24 | // /home/dfc/go/src/testing/example.go:114 25 | // testing.RunExamples 26 | // /home/dfc/go/src/testing/example.go:38 27 | // testing.(*M).Run 28 | // /home/dfc/go/src/testing/testing.go:744 29 | // main.main 30 | // /github.com/pkg/errors/_test/_testmain.go:106 31 | // runtime.main 32 | // /home/dfc/go/src/runtime/proc.go:183 33 | // runtime.goexit 34 | // /home/dfc/go/src/runtime/asm_amd64.s:2059 35 | } 36 | 37 | func ExampleWithMessage() { 38 | cause := errors.New("whoops") 39 | err := errors.WithMessage(cause, "oh noes") 40 | fmt.Println(err) 41 | 42 | // Output: oh noes: whoops 43 | } 44 | 45 | func ExampleWithStack() { 46 | cause := errors.New("whoops") 47 | err := errors.WithStack(cause) 48 | fmt.Println(err) 49 | 50 | // Output: whoops 51 | } 52 | 53 | func ExampleWithStack_printf() { 54 | cause := errors.New("whoops") 55 | err := errors.WithStack(cause) 56 | fmt.Printf("%+v", err) 57 | 58 | // Example Output: 59 | // whoops 60 | // github.com/pkg/errors_test.ExampleWithStack_printf 61 | // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55 62 | // testing.runExample 63 | // /usr/lib/go/src/testing/example.go:114 64 | // testing.RunExamples 65 | // /usr/lib/go/src/testing/example.go:38 66 | // testing.(*M).Run 67 | // /usr/lib/go/src/testing/testing.go:744 68 | // main.main 69 | // github.com/pkg/errors/_test/_testmain.go:106 70 | // runtime.main 71 | // /usr/lib/go/src/runtime/proc.go:183 72 | // runtime.goexit 73 | // /usr/lib/go/src/runtime/asm_amd64.s:2086 74 | // github.com/pkg/errors_test.ExampleWithStack_printf 75 | // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56 76 | // testing.runExample 77 | // /usr/lib/go/src/testing/example.go:114 78 | // testing.RunExamples 79 | // /usr/lib/go/src/testing/example.go:38 80 | // testing.(*M).Run 81 | // /usr/lib/go/src/testing/testing.go:744 82 | // main.main 83 | // github.com/pkg/errors/_test/_testmain.go:106 84 | // runtime.main 85 | // /usr/lib/go/src/runtime/proc.go:183 86 | // runtime.goexit 87 | // /usr/lib/go/src/runtime/asm_amd64.s:2086 88 | } 89 | 90 | func ExampleWrap() { 91 | cause := errors.New("whoops") 92 | err := errors.Wrap(cause, "oh noes") 93 | fmt.Println(err) 94 | 95 | // Output: oh noes: whoops 96 | } 97 | 98 | func fn() error { 99 | e1 := errors.New("error") 100 | e2 := errors.Wrap(e1, "inner") 101 | e3 := errors.Wrap(e2, "middle") 102 | return errors.Wrap(e3, "outer") 103 | } 104 | 105 | func ExampleCause() { 106 | err := fn() 107 | fmt.Println(err) 108 | fmt.Println(errors.Cause(err)) 109 | 110 | // Output: outer: middle: inner: error 111 | // error 112 | } 113 | 114 | func ExampleWrap_extended() { 115 | err := fn() 116 | fmt.Printf("%+v\n", err) 117 | 118 | // Example output: 119 | // error 120 | // github.com/pkg/errors_test.fn 121 | // /home/dfc/src/github.com/pkg/errors/example_test.go:47 122 | // github.com/pkg/errors_test.ExampleCause_printf 123 | // /home/dfc/src/github.com/pkg/errors/example_test.go:63 124 | // testing.runExample 125 | // /home/dfc/go/src/testing/example.go:114 126 | // testing.RunExamples 127 | // /home/dfc/go/src/testing/example.go:38 128 | // testing.(*M).Run 129 | // /home/dfc/go/src/testing/testing.go:744 130 | // main.main 131 | // /github.com/pkg/errors/_test/_testmain.go:104 132 | // runtime.main 133 | // /home/dfc/go/src/runtime/proc.go:183 134 | // runtime.goexit 135 | // /home/dfc/go/src/runtime/asm_amd64.s:2059 136 | // github.com/pkg/errors_test.fn 137 | // /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner 138 | // github.com/pkg/errors_test.fn 139 | // /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle 140 | // github.com/pkg/errors_test.fn 141 | // /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer 142 | } 143 | 144 | func ExampleWrapf() { 145 | cause := errors.New("whoops") 146 | err := errors.Wrapf(cause, "oh noes #%d", 2) 147 | fmt.Println(err) 148 | 149 | // Output: oh noes #2: whoops 150 | } 151 | 152 | func ExampleErrorf_extended() { 153 | err := errors.Errorf("whoops: %s", "foo") 154 | fmt.Printf("%+v", err) 155 | 156 | // Example output: 157 | // whoops: foo 158 | // github.com/pkg/errors_test.ExampleErrorf 159 | // /home/dfc/src/github.com/pkg/errors/example_test.go:101 160 | // testing.runExample 161 | // /home/dfc/go/src/testing/example.go:114 162 | // testing.RunExamples 163 | // /home/dfc/go/src/testing/example.go:38 164 | // testing.(*M).Run 165 | // /home/dfc/go/src/testing/testing.go:744 166 | // main.main 167 | // /github.com/pkg/errors/_test/_testmain.go:102 168 | // runtime.main 169 | // /home/dfc/go/src/runtime/proc.go:183 170 | // runtime.goexit 171 | // /home/dfc/go/src/runtime/asm_amd64.s:2059 172 | } 173 | 174 | func Example_stackTrace() { 175 | type stackTracer interface { 176 | StackTrace() errors.StackTrace 177 | } 178 | 179 | err, ok := errors.Cause(fn()).(stackTracer) 180 | if !ok { 181 | panic("oops, err does not implement stackTracer") 182 | } 183 | 184 | st := err.StackTrace() 185 | fmt.Printf("%+v", st[0:2]) // top two frames 186 | 187 | // Example output: 188 | // github.com/pkg/errors_test.fn 189 | // /home/dfc/src/github.com/pkg/errors/example_test.go:47 190 | // github.com/pkg/errors_test.Example_stackTrace 191 | // /home/dfc/src/github.com/pkg/errors/example_test.go:127 192 | } 193 | 194 | func ExampleCause_printf() { 195 | err := errors.Wrap(func() error { 196 | return func() error { 197 | return errors.Errorf("hello %s", fmt.Sprintf("world")) 198 | }() 199 | }(), "failed") 200 | 201 | fmt.Printf("%v", err) 202 | 203 | // Output: failed: hello world 204 | } 205 | -------------------------------------------------------------------------------- /stack.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "path" 8 | "runtime" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // StackTracer retrieves the StackTrace 14 | // Generally you would want to use the GetStackTracer function to do that. 15 | type StackTracer interface { 16 | StackTrace() StackTrace 17 | // Empty returns true if the stack trace is empty, StackTrace might clone the 18 | // stack trace, add this method to avoid unnecessary clone. 19 | Empty() bool 20 | } 21 | 22 | // GetStackTracer will return the first StackTracer in the causer chain. 23 | // This function is used by AddStack to avoid creating redundant stack traces. 24 | // 25 | // You can also use the StackTracer interface on the returned error to get the stack trace. 26 | func GetStackTracer(origErr error) StackTracer { 27 | var stacked StackTracer 28 | WalkDeep(origErr, func(err error) bool { 29 | if stackTracer, ok := err.(StackTracer); ok { 30 | stacked = stackTracer 31 | return true 32 | } 33 | return false 34 | }) 35 | return stacked 36 | } 37 | 38 | // Frame represents a program counter inside a stack frame. 39 | type Frame uintptr 40 | 41 | // pc returns the program counter for this frame; 42 | // multiple frames may have the same PC value. 43 | func (f Frame) pc() uintptr { return uintptr(f) - 1 } 44 | 45 | // file returns the full path to the file that contains the 46 | // function for this Frame's pc. 47 | func (f Frame) file() string { 48 | fn := runtime.FuncForPC(f.pc()) 49 | if fn == nil { 50 | return "unknown" 51 | } 52 | file, _ := fn.FileLine(f.pc()) 53 | return file 54 | } 55 | 56 | // line returns the line number of source code of the 57 | // function for this Frame's pc. 58 | func (f Frame) line() int { 59 | fn := runtime.FuncForPC(f.pc()) 60 | if fn == nil { 61 | return 0 62 | } 63 | _, line := fn.FileLine(f.pc()) 64 | return line 65 | } 66 | 67 | // Format formats the frame according to the fmt.Formatter interface. 68 | // 69 | // %s source file 70 | // %d source line 71 | // %n function name 72 | // %v equivalent to %s:%d 73 | // 74 | // Format accepts flags that alter the printing of some verbs, as follows: 75 | // 76 | // %+s function name and path of source file relative to the compile time 77 | // GOPATH separated by \n\t (\n\t) 78 | // %+v equivalent to %+s:%d 79 | func (f Frame) Format(s fmt.State, verb rune) { 80 | f.format(s, s, verb) 81 | } 82 | 83 | // format allows stack trace printing calls to be made with a bytes.Buffer. 84 | func (f Frame) format(w io.Writer, s fmt.State, verb rune) { 85 | switch verb { 86 | case 's': 87 | switch { 88 | case s.Flag('+'): 89 | pc := f.pc() 90 | fn := runtime.FuncForPC(pc) 91 | if fn == nil { 92 | io.WriteString(w, "unknown") 93 | } else { 94 | file, _ := fn.FileLine(pc) 95 | io.WriteString(w, fn.Name()) 96 | io.WriteString(w, "\n\t") 97 | io.WriteString(w, file) 98 | } 99 | default: 100 | io.WriteString(w, path.Base(f.file())) 101 | } 102 | case 'd': 103 | io.WriteString(w, strconv.Itoa(f.line())) 104 | case 'n': 105 | name := runtime.FuncForPC(f.pc()).Name() 106 | io.WriteString(w, funcname(name)) 107 | case 'v': 108 | f.format(w, s, 's') 109 | io.WriteString(w, ":") 110 | f.format(w, s, 'd') 111 | } 112 | } 113 | 114 | // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). 115 | type StackTrace []Frame 116 | 117 | // Format formats the stack of Frames according to the fmt.Formatter interface. 118 | // 119 | // %s lists source files for each Frame in the stack 120 | // %v lists the source file and line number for each Frame in the stack 121 | // 122 | // Format accepts flags that alter the printing of some verbs, as follows: 123 | // 124 | // %+v Prints filename, function, and line number for each Frame in the stack. 125 | func (st StackTrace) Format(s fmt.State, verb rune) { 126 | var b bytes.Buffer 127 | switch verb { 128 | case 'v': 129 | switch { 130 | case s.Flag('+'): 131 | b.Grow(len(st) * stackMinLen) 132 | for _, fr := range st { 133 | b.WriteByte('\n') 134 | fr.format(&b, s, verb) 135 | } 136 | case s.Flag('#'): 137 | fmt.Fprintf(&b, "%#v", []Frame(st)) 138 | default: 139 | st.formatSlice(&b, s, verb) 140 | } 141 | case 's': 142 | st.formatSlice(&b, s, verb) 143 | } 144 | io.Copy(s, &b) 145 | } 146 | 147 | // formatSlice will format this StackTrace into the given buffer as a slice of 148 | // Frame, only valid when called with '%s' or '%v'. 149 | func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { 150 | b.WriteByte('[') 151 | if len(st) == 0 { 152 | b.WriteByte(']') 153 | return 154 | } 155 | 156 | b.Grow(len(st) * (stackMinLen / 4)) 157 | st[0].format(b, s, verb) 158 | for _, fr := range st[1:] { 159 | b.WriteByte(' ') 160 | fr.format(b, s, verb) 161 | } 162 | b.WriteByte(']') 163 | } 164 | 165 | // stackMinLen is a best-guess at the minimum length of a stack trace. It 166 | // doesn't need to be exact, just give a good enough head start for the buffer 167 | // to avoid the expensive early growth. 168 | const stackMinLen = 96 169 | 170 | // stack represents a stack of program counters. 171 | type stack []uintptr 172 | 173 | func (s *stack) Format(st fmt.State, verb rune) { 174 | switch verb { 175 | case 'v': 176 | switch { 177 | case st.Flag('+'): 178 | var b bytes.Buffer 179 | b.Grow(len(*s) * stackMinLen) 180 | for _, pc := range *s { 181 | f := Frame(pc) 182 | b.WriteByte('\n') 183 | f.format(&b, st, 'v') 184 | } 185 | io.Copy(st, &b) 186 | } 187 | } 188 | } 189 | 190 | func (s *stack) StackTrace() StackTrace { 191 | f := make([]Frame, len(*s)) 192 | for i := 0; i < len(f); i++ { 193 | f[i] = Frame((*s)[i]) 194 | } 195 | return f 196 | } 197 | 198 | func (s *stack) Empty() bool { 199 | return len(*s) == 0 200 | } 201 | 202 | func callers() *stack { 203 | return callersSkip(4) 204 | } 205 | 206 | func callersSkip(skip int) *stack { 207 | const depth = 32 208 | var pcs [depth]uintptr 209 | n := runtime.Callers(skip, pcs[:]) 210 | var st stack = pcs[0:n] 211 | return &st 212 | } 213 | 214 | // funcname removes the path prefix component of a function's name reported by func.Name(). 215 | func funcname(name string) string { 216 | i := strings.LastIndex(name, "/") 217 | name = name[i+1:] 218 | i = strings.Index(name, ".") 219 | return name[i+1:] 220 | } 221 | 222 | // NewStack is for library implementers that want to generate a stack trace. 223 | // Normally you should insted use AddStack to get an error with a stack trace. 224 | // 225 | // The result of this function can be turned into a stack trace by calling .StackTrace() 226 | // 227 | // This function takes an argument for the number of stack frames to skip. 228 | // This avoids putting stack generation function calls like this one in the stack trace. 229 | // A value of 0 will give you the line that called NewStack(0) 230 | // A library author wrapping this in their own function will want to use a value of at least 1. 231 | func NewStack(skip int) StackTracer { 232 | return callersSkip(skip + 3) 233 | } 234 | -------------------------------------------------------------------------------- /stack_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "runtime" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | var initpc, _, _, _ = runtime.Caller(0) 12 | 13 | func TestFrameLine(t *testing.T) { 14 | var tests = []struct { 15 | Frame 16 | want int 17 | }{{ 18 | Frame(initpc), 19 | 11, 20 | }, { 21 | func() Frame { 22 | var pc, _, _, _ = runtime.Caller(0) 23 | return Frame(pc) 24 | }(), 25 | 22, 26 | }, /* { // TODO stdlib `runtime` Behavior changed between 1.13 and 1.14 27 | func() Frame { 28 | var pc, _, _, _ = runtime.Caller(1) 29 | return Frame(pc) 30 | }(), 31 | 24, 32 | }, */{ 33 | Frame(0), // invalid PC 34 | 0, 35 | }} 36 | 37 | for _, tt := range tests { 38 | got := tt.Frame.line() 39 | want := tt.want 40 | if want != got { 41 | t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) 42 | } 43 | } 44 | } 45 | 46 | type X struct{} 47 | 48 | func (x X) val() Frame { 49 | var pc, _, _, _ = runtime.Caller(0) 50 | return Frame(pc) 51 | } 52 | 53 | func (x *X) ptr() Frame { 54 | var pc, _, _, _ = runtime.Caller(0) 55 | return Frame(pc) 56 | } 57 | 58 | func TestFrameFormat(t *testing.T) { 59 | var tests = []struct { 60 | Frame 61 | format string 62 | want string 63 | }{{ 64 | Frame(initpc), 65 | "%s", 66 | "stack_test.go", 67 | }, { 68 | Frame(initpc), 69 | "%+s", 70 | "github.com/pingcap/errors.init\n" + 71 | "\t.+/pingcap/errors/stack_test.go", 72 | }, { 73 | Frame(0), 74 | "%s", 75 | "unknown", 76 | }, { 77 | Frame(0), 78 | "%+s", 79 | "unknown", 80 | }, { 81 | Frame(initpc), 82 | "%d", 83 | "11", 84 | }, { 85 | Frame(0), 86 | "%d", 87 | "0", 88 | }, { 89 | Frame(initpc), 90 | "%n", 91 | "init", 92 | }, { 93 | func() Frame { 94 | var x X 95 | return x.ptr() 96 | }(), 97 | "%n", 98 | `\(\*X\).ptr`, 99 | }, { 100 | func() Frame { 101 | var x X 102 | return x.val() 103 | }(), 104 | "%n", 105 | "X.val", 106 | }, { 107 | Frame(0), 108 | "%n", 109 | "", 110 | }, { 111 | Frame(initpc), 112 | "%v", 113 | "stack_test.go:11", 114 | }, { 115 | Frame(initpc), 116 | "%+v", 117 | "github.com/pingcap/errors.init\n" + 118 | "\t.+/pingcap/errors/stack_test.go:11", 119 | }, { 120 | Frame(0), 121 | "%v", 122 | "unknown:0", 123 | }} 124 | 125 | for i, tt := range tests { 126 | testFormatRegexp(t, i, tt.Frame, tt.format, tt.want) 127 | } 128 | } 129 | 130 | func TestFuncname(t *testing.T) { 131 | tests := []struct { 132 | name, want string 133 | }{ 134 | {"", ""}, 135 | {"runtime.main", "main"}, 136 | {"github.com/pingcap/errors.funcname", "funcname"}, 137 | {"funcname", "funcname"}, 138 | {"io.copyBuffer", "copyBuffer"}, 139 | {"main.(*R).Write", "(*R).Write"}, 140 | } 141 | 142 | for _, tt := range tests { 143 | got := funcname(tt.name) 144 | want := tt.want 145 | if got != want { 146 | t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got) 147 | } 148 | } 149 | } 150 | 151 | func TestStackTrace(t *testing.T) { 152 | tests := []struct { 153 | err error 154 | want []string 155 | }{{ 156 | New("ooh"), []string{ 157 | "github.com/pingcap/errors.TestStackTrace\n" + 158 | "\t.+/pingcap/errors/stack_test.go", 159 | }, 160 | }, { 161 | Annotate(New("ooh"), "ahh"), []string{ 162 | "github.com/pingcap/errors.TestStackTrace\n" + 163 | "\t.+/pingcap/errors/stack_test.go", // this is the stack of Wrap, not New 164 | }, 165 | }, { 166 | Cause(Annotate(New("ooh"), "ahh")), []string{ 167 | "github.com/pingcap/errors.TestStackTrace\n" + 168 | "\t.+/pingcap/errors/stack_test.go", // this is the stack of New 169 | }, 170 | }, { 171 | func() error { return New("ooh") }(), []string{ 172 | `github.com/pingcap/errors.(func·009|TestStackTrace.func1)` + 173 | "\n\t.+/pingcap/errors/stack_test.go", // this is the stack of New 174 | "github.com/pingcap/errors.TestStackTrace\n" + 175 | "\t.+/pingcap/errors/stack_test.go", // this is the stack of New's caller 176 | }, 177 | }, { 178 | Cause(func() error { 179 | return func() error { 180 | return Errorf("hello %s", fmt.Sprintf("world")) 181 | }() 182 | }()), []string{ 183 | // go 1.23 when Debug its suffix is "TestStackTrace.func2.1", it's 184 | // "TestStackTrace.TestStackTrace.func2.func3" when Run 185 | `github.com/pingcap/errors.(func·010|TestStackTrace.func2.1|TestStackTrace.TestStackTrace.func2.func3)` + 186 | "\n\t.+/pingcap/errors/stack_test.go", // this is the stack of Errorf 187 | `github.com/pingcap/errors.(func·011|TestStackTrace.func2)` + 188 | "\n\t.+/pingcap/errors/stack_test.go", // this is the stack of Errorf's caller 189 | "github.com/pingcap/errors.TestStackTrace\n" + 190 | "\t.+/pingcap/errors/stack_test.go", // this is the stack of Errorf's caller's caller 191 | }, 192 | }} 193 | for i, tt := range tests { 194 | ste, ok := tt.err.(interface { 195 | StackTrace() StackTrace 196 | }) 197 | if !ok { 198 | ste = tt.err.(interface { 199 | Cause() error 200 | }).Cause().(interface { 201 | StackTrace() StackTrace 202 | }) 203 | } 204 | st := ste.StackTrace() 205 | for j, want := range tt.want { 206 | testFormatRegexp(t, i, st[j], "%+v", want) 207 | } 208 | } 209 | } 210 | 211 | // This comment helps to maintain original line numbers 212 | // Perhaps this test is too fragile :) 213 | func stackTrace() StackTrace { 214 | return NewStack(0).StackTrace() 215 | // This comment helps to maintain original line numbers 216 | // Perhaps this test is too fragile :) 217 | } 218 | 219 | func TestStackTraceFormat(t *testing.T) { 220 | tests := []struct { 221 | StackTrace 222 | format string 223 | want string 224 | }{{ 225 | nil, 226 | "%s", 227 | `\[\]`, 228 | }, { 229 | nil, 230 | "%v", 231 | `\[\]`, 232 | }, { 233 | nil, 234 | "%+v", 235 | "", 236 | }, { 237 | nil, 238 | "%#v", 239 | `\[\]errors.Frame\(nil\)`, 240 | }, { 241 | make(StackTrace, 0), 242 | "%s", 243 | `\[\]`, 244 | }, { 245 | make(StackTrace, 0), 246 | "%v", 247 | `\[\]`, 248 | }, { 249 | make(StackTrace, 0), 250 | "%+v", 251 | "", 252 | }, { 253 | make(StackTrace, 0), 254 | "%#v", 255 | `\[\]errors.Frame{}`, 256 | }, { 257 | stackTrace()[:2], 258 | "%s", 259 | `\[stack_test.go stack_test.go\]`, 260 | }, { 261 | stackTrace()[:2], 262 | "%v", 263 | `[stack_test.go:207 stack_test.go:254]`, 264 | }, { 265 | stackTrace()[:2], 266 | "%+v", 267 | "\n" + 268 | "github.com/pingcap/errors.stackTrace\n" + 269 | "\t.+/pingcap/errors/stack_test.go:\\d+\n" + 270 | "github.com/pingcap/errors.TestStackTraceFormat\n" + 271 | "\t.+/pingcap/errors/stack_test.go:\\d+", 272 | }, { 273 | stackTrace()[:2], 274 | "%#v", 275 | `\[\]errors.Frame{stack_test.go:\d+, stack_test.go:\d+}`, 276 | }} 277 | 278 | for i, tt := range tests { 279 | testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) 280 | } 281 | } 282 | 283 | func TestNewStack(t *testing.T) { 284 | got := NewStack(1).StackTrace() 285 | want := NewStack(1).StackTrace() 286 | if got[0] != want[0] { 287 | t.Errorf("NewStack(remove NewStack): want: %v, got: %v", want, got) 288 | } 289 | gotFirst := fmt.Sprintf("%+v", got[0])[0:15] 290 | if gotFirst != "testing.tRunner" { 291 | t.Errorf("NewStack(): want: %v, got: %+v", "testing.tRunner", gotFirst) 292 | } 293 | } 294 | 295 | func TestNewNoStackError(t *testing.T) { 296 | err := NewNoStackError("test error") 297 | err = Trace(err) 298 | err = Trace(err) 299 | result := fmt.Sprintf("%+v", err) 300 | if !strings.Contains(result, "test error") || 301 | !strings.Contains(result, "pingcap/errors.TestNewNoStackError") { 302 | t.Errorf("NewNoStackError(): want %s, got %v", "test error", result) 303 | } 304 | } 305 | 306 | func TestNewNoStackErrorf(t *testing.T) { 307 | err := NewNoStackErrorf("test error %s", "yes") 308 | err = Trace(err) 309 | err = Trace(err) 310 | result := fmt.Sprintf("%+v", err) 311 | if !strings.Contains(result, "test error yes") || 312 | !strings.Contains(result, "pingcap/errors.TestNewNoStackErrorf") { 313 | t.Errorf("NewNoStackError(): want %s, got %v", "test error", result) 314 | } 315 | } 316 | 317 | func TestSuspendError(t *testing.T) { 318 | err := io.EOF 319 | err = SuspendStack(err) 320 | err = Trace(err) 321 | result := fmt.Sprintf("%+v", err) 322 | if !strings.Contains(result, "EOF") || 323 | !strings.Contains(result, "pingcap/errors.TestSuspendError") { 324 | t.Errorf("NewNoStackError(): want %s, got %v", "EOF", result) 325 | } 326 | if io.EOF != Cause(err) { 327 | t.Errorf("SuspendStackError can not got back origion error.") 328 | } 329 | } 330 | 331 | func TestSuspendTracedWithMessageError(t *testing.T) { 332 | tracedErr := Trace(io.EOF) 333 | tracedErr = WithStack(tracedErr) 334 | tracedErr = WithMessage(tracedErr, "1") 335 | tracedErr = SuspendStack(tracedErr) 336 | tracedErr = Trace(tracedErr) 337 | result := fmt.Sprintf("%+v", tracedErr) 338 | if result != "EOF\n1" { 339 | t.Errorf("NewNoStackError(): want %s, got %v", "EOF\n1", result) 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /errdoc-gen/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "flag" 18 | "fmt" 19 | "go/ast" 20 | "go/parser" 21 | "go/token" 22 | "os" 23 | "os/exec" 24 | "path/filepath" 25 | "strings" 26 | "text/template" 27 | ) 28 | 29 | var opt struct { 30 | source string 31 | module string 32 | output string 33 | ignore string 34 | retainCode bool 35 | } 36 | 37 | func init() { 38 | flag.StringVar(&opt.source, "source", "", "The source directory of error documentation") 39 | flag.StringVar(&opt.module, "module", "", "The module name of target repository") 40 | flag.StringVar(&opt.output, "output", "", "The output path of error documentation file") 41 | flag.StringVar(&opt.ignore, "ignore", "", "Directories to ignore, splitted by comma") 42 | flag.BoolVar(&opt.retainCode, "retain-code", false, "Retain the generated code when generator exit") 43 | } 44 | 45 | func log(format string, args ...interface{}) { 46 | fmt.Println(fmt.Sprintf(format, args...)) 47 | } 48 | 49 | func fatal(format string, args ...interface{}) { 50 | log(format, args...) 51 | os.Exit(1) 52 | } 53 | 54 | const autoDirectoryName = "_errdoc-generator" 55 | const entryFileName = "main.go" 56 | 57 | func main() { 58 | flag.Parse() 59 | if opt.source == "" { 60 | fatal("The source directory cannot be empty") 61 | } 62 | 63 | source, err := filepath.EvalSymlinks(opt.source) 64 | if err != nil { 65 | fatal("Evaluate symbol link path %s failed: %v", opt.source, err) 66 | } 67 | opt.source = source 68 | 69 | _, err = os.Stat(filepath.Join(opt.source, "go.mod")) 70 | if os.IsNotExist(err) { 71 | fatal("The source directory is not the root path of codebase(go.mod not found)") 72 | } 73 | 74 | if opt.output == "" { 75 | opt.output = filepath.Join(opt.source, "errors.toml") 76 | log("The --output argument is missing, default to %s", opt.output) 77 | } 78 | 79 | errNames, err := errdoc(opt.source, opt.module) 80 | if err != nil { 81 | log("Extract the error documentation failed: %+v", err) 82 | } 83 | 84 | targetDir := filepath.Join(opt.source, autoDirectoryName) 85 | if err := os.MkdirAll(targetDir, 0755); err != nil { 86 | fatal("Cannot create the errdoc: %+v", err) 87 | } 88 | 89 | if !opt.retainCode { 90 | defer os.RemoveAll(targetDir) 91 | } 92 | 93 | tmpl := ` 94 | package main 95 | 96 | import ( 97 | "bytes" 98 | "flag" 99 | "io/ioutil" 100 | "os" 101 | "reflect" 102 | "fmt" 103 | "sort" 104 | "strings" 105 | 106 | "github.com/BurntSushi/toml" 107 | "github.com/pingcap/errors" 108 | {{- range $decl := .}} 109 | {{$decl.PackageName}} "{{- $decl.ImportPath}}" 110 | {{- end}} 111 | ) 112 | 113 | func main() { 114 | var outpath string 115 | flag.StringVar(&outpath, "output", "", "Specify the error documentation output file path") 116 | flag.Parse() 117 | if outpath == "" { 118 | println("Usage: ./_errdoc-generator --output /path/to/errors.toml") 119 | os.Exit(1) 120 | } 121 | 122 | // Read-in the exists file and merge the description/workaround from exists file 123 | existDefinition := map[string]spec{} 124 | if file, err := ioutil.ReadFile(outpath); err == nil { 125 | err = toml.Unmarshal(file, &existDefinition) 126 | if err != nil { 127 | println(fmt.Sprintf("Invalid toml file %s when merging exists description/workaround: %v", outpath, err)) 128 | os.Exit(1) 129 | } 130 | } 131 | 132 | var allErrors []error 133 | {{- range $decl := .}} 134 | {{- range $err := $decl.ErrNames}} 135 | allErrors = append(allErrors, {{$decl.PackageName}}.{{- $err}}) 136 | {{- end}} 137 | {{- end}} 138 | 139 | var dedup = map[string]spec{} 140 | for _, e := range allErrors { 141 | terr, ok := e.(*errors.Error) 142 | if !ok { 143 | println("Non-normalized error:", e.Error()) 144 | } else { 145 | val := reflect.ValueOf(terr).Elem() 146 | codeText := val.FieldByName("codeText") 147 | message := val.FieldByName("message") 148 | if previous, found := dedup[codeText.String()]; found { 149 | println("Duplicated error code:", codeText.String()) 150 | if message.String() < previous.Error { 151 | continue 152 | } 153 | } 154 | s := spec{ 155 | Code: codeText.String(), 156 | Error: message.String(), 157 | } 158 | if exist, found := existDefinition[s.Code]; found { 159 | s.Description = strings.TrimSpace(exist.Description) 160 | s.Workaround = strings.TrimSpace(exist.Workaround) 161 | } 162 | dedup[codeText.String()] = s 163 | } 164 | } 165 | 166 | var sorted []spec 167 | for _, item := range dedup { 168 | sorted = append(sorted, item) 169 | } 170 | sort.Slice(sorted, func(i, j int) bool { 171 | // TiDB exits duplicated code 172 | if sorted[i].Code == sorted[j].Code { 173 | return sorted[i].Error < sorted[j].Error 174 | } 175 | return sorted[i].Code < sorted[j].Code 176 | }) 177 | 178 | // We don't use toml library to serialize it due to cannot reserve the order for map[string]spec 179 | buffer := bytes.NewBufferString("# AUTOGENERATED BY github.com/pingcap/errors/errdoc-gen\n" + 180 | "# YOU CAN CHANGE THE 'description'/'workaround' FIELDS IF THEM ARE IMPROPER.\n\n") 181 | for _, item := range sorted { 182 | buffer.WriteString(fmt.Sprintf("[\"%s\"]\nerror = '''\n%s\n'''\n", item.Code, item.Error)) 183 | if item.Description != "" { 184 | buffer.WriteString(fmt.Sprintf("description = '''\n%s\n'''\n", item.Description)) 185 | } 186 | if item.Workaround != "" { 187 | buffer.WriteString(fmt.Sprintf("workaround = '''\n%s\n'''\n", item.Workaround)) 188 | } 189 | buffer.WriteString("\n") 190 | } 191 | if err := ioutil.WriteFile(outpath, buffer.Bytes(), 0644); err != nil { 192 | panic(err) 193 | } 194 | } 195 | ` + "type spec struct {\n" + 196 | "Code string\n" + 197 | "Error string `toml:\"error\"`\n" + 198 | "Description string `toml:\"description\"`\n" + 199 | "Workaround string `toml:\"workaround\"`\n" + 200 | "}" 201 | 202 | t, err := template.New("_errdoc-template").Parse(tmpl) 203 | if err != nil { 204 | fatal("Parse template failed: %+v", err) 205 | } 206 | 207 | outFile := filepath.Join(targetDir, entryFileName) 208 | out, err := os.OpenFile(outFile, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) 209 | if err != nil { 210 | fatal("Open %s failed: %+v", outFile, err) 211 | } 212 | defer out.Close() 213 | 214 | if err := t.Execute(out, errNames); err != nil { 215 | fatal("Render template failed: %+v", err) 216 | } 217 | 218 | output, err := filepath.Abs(opt.output) 219 | if err != nil { 220 | fatal("Evaluate the absolute path of output failed: %+v", err) 221 | } 222 | 223 | cmd := exec.Command("go", "run", filepath.Join(autoDirectoryName, entryFileName), "--output", output) 224 | cmd.Dir = opt.source 225 | data, err := cmd.CombinedOutput() 226 | if err != nil { 227 | fatal("Generate %s failed: %v, output:\n%s", opt.output, err, string(data)) 228 | } 229 | 230 | log("Generate successfully, output:\n%s", string(data)) 231 | } 232 | 233 | type errDecl struct { 234 | ImportPath string 235 | PackageName string 236 | ErrNames []string 237 | } 238 | 239 | func errdoc(source, module string) ([]*errDecl, error) { 240 | source, err := filepath.Abs(source) 241 | if err != nil { 242 | return nil, err 243 | } 244 | 245 | dedup := map[string]*errDecl{} 246 | 247 | ignored := strings.Split(opt.ignore, ",") 248 | for i := range ignored { 249 | ignored[i] = filepath.Join(source, ignored[i]) 250 | } 251 | err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 252 | if err != nil { 253 | return err 254 | } 255 | if info.IsDir() { 256 | for i := range ignored { 257 | if ignored[i] == path { 258 | return filepath.SkipDir 259 | } 260 | } 261 | return nil 262 | } 263 | if !strings.HasSuffix(path, ".go") { 264 | return nil 265 | } 266 | fset := token.NewFileSet() 267 | file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) 268 | if err != nil { 269 | // Ignore invalid source file 270 | return nil 271 | } 272 | errNames := export(file) 273 | if len(errNames) < 1 { 274 | return nil 275 | } 276 | dirPath := filepath.Dir(path) 277 | subPath, err := filepath.Rel(source, dirPath) 278 | if err != nil { 279 | return err 280 | } 281 | packageName := strings.ReplaceAll(subPath, "/", "_") 282 | if decl, found := dedup[packageName]; found { 283 | decl.ErrNames = append(decl.ErrNames, errNames...) 284 | } else { 285 | decl := &errDecl{ 286 | ImportPath: filepath.Join(module, subPath), 287 | PackageName: packageName, 288 | ErrNames: errNames, 289 | } 290 | dedup[packageName] = decl 291 | } 292 | return nil 293 | }) 294 | 295 | var errDecls []*errDecl 296 | for _, decl := range dedup { 297 | errDecls = append(errDecls, decl) 298 | } 299 | 300 | return errDecls, err 301 | } 302 | 303 | func export(f *ast.File) []string { 304 | if len(f.Decls) == 0 { 305 | return nil 306 | } 307 | 308 | var errNames []string 309 | for _, decl := range f.Decls { 310 | gen, ok := decl.(*ast.GenDecl) 311 | if !ok || len(gen.Specs) == 0 { 312 | continue 313 | } 314 | for _, spec := range gen.Specs { 315 | switch errSpec := spec.(type) { 316 | case *ast.ValueSpec: 317 | // CASES: 318 | // var ErrXXX = errors.Normalize(string, opts...) 319 | // var ( 320 | // ErrYYY = errors.Normalize(string, opts...) 321 | // ErrZZZ = errors.Normalize(string, opts...) 322 | // A = errors.Normalize(string, opts...) 323 | // ) 324 | // var ErrXXX, ErrYYY = errors.Normalize(string, opts...), errors.Normalize(string, opts...) 325 | // var ( 326 | // ErrYYY = errors.Normalize(string, opts...) 327 | // ErrZZZ, ErrWWW = errors.Normalize(string, opts...), errors.Normalize(string, opts...) 328 | // A = errors.Normalize(string, opts...) 329 | // ) 330 | // 331 | if len(errSpec.Names) != len(errSpec.Values) { 332 | continue 333 | } 334 | for i, name := range errSpec.Names { 335 | if !strings.HasPrefix(name.Name, "Err") { 336 | continue 337 | } 338 | _, ok := errSpec.Values[i].(*ast.CallExpr) 339 | if !ok { 340 | continue 341 | } 342 | errNames = append(errNames, name.Name) 343 | } 344 | default: 345 | continue 346 | } 347 | } 348 | } 349 | return errNames 350 | } 351 | -------------------------------------------------------------------------------- /normalize.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package errors 15 | 16 | import ( 17 | "fmt" 18 | "runtime" 19 | "strconv" 20 | 21 | "go.uber.org/atomic" 22 | ) 23 | 24 | var _ fmt.Formatter = (*redactFormatter)(nil) 25 | 26 | // RedactLogEnabled defines whether the arguments of Error need to be redacted. 27 | var RedactLogEnabled atomic.String 28 | 29 | const ( 30 | RedactLogEnable string = "ON" 31 | RedactLogDisable = "OFF" 32 | RedactLogMarker = "MARKER" 33 | ) 34 | 35 | // ErrCode represents a specific error type in a error class. 36 | // Same error code can be used in different error classes. 37 | type ErrCode int 38 | 39 | // ErrCodeText is a textual error code that represents a specific error type in a error class. 40 | type ErrCodeText string 41 | 42 | type ErrorID string 43 | type RFCErrorCode string 44 | 45 | // Error is the 'prototype' of a type of errors. 46 | // Use DefineError to make a *Error: 47 | // var ErrUnavailable = errors.Normalize("Region %d is unavailable", errors.RFCCodeText("Unavailable")) 48 | // 49 | // "throw" it at runtime: 50 | // 51 | // func Somewhat() error { 52 | // ... 53 | // if err != nil { 54 | // // generate a stackful error use the message template at defining, 55 | // // also see FastGen(it's stackless), GenWithStack(it uses custom message template). 56 | // return ErrUnavailable.GenWithStackByArgs(region.ID) 57 | // } 58 | // } 59 | // 60 | // testing whether an error belongs to a prototype: 61 | // 62 | // if ErrUnavailable.Equal(err) { 63 | // // handle this error. 64 | // } 65 | type Error struct { 66 | code ErrCode 67 | // codeText is the textual describe of the error code 68 | codeText ErrCodeText 69 | // message is a template of the description of this error. 70 | // printf-style formatting is enabled. 71 | message string 72 | // redactArgsPos defines the positions of arguments in message that need to be redacted. 73 | // And it is controlled by the global var RedactLogEnabled. 74 | // For example, an original error is `Duplicate entry 'PRIMARY' for key 'key'`, 75 | // when RedactLogEnabled is ON and redactArgsPos is [0, 1], the error is `Duplicate entry '?' for key '?'`. 76 | // when RedactLogEnabled is MARKER and redactArgsPos is [0, 1], the error is `Duplicate entry '‹..›' for key '‹..›'`. 77 | redactArgsPos []int 78 | // Cause is used to warp some third party error. 79 | cause error 80 | args []interface{} 81 | file string 82 | line int 83 | } 84 | 85 | var _ messenger = (*Error)(nil) 86 | 87 | // Code returns the numeric code of this error. 88 | // ID() will return textual error if there it is, 89 | // when you just want to get the purely numeric error 90 | // (e.g., for mysql protocol transmission.), this would be useful. 91 | func (e *Error) Code() ErrCode { 92 | return e.code 93 | } 94 | 95 | // Code returns ErrorCode, by the RFC: 96 | // 97 | // The error code is a 3-tuple of abbreviated component name, error class and error code, 98 | // joined by a colon like {Component}:{ErrorClass}:{InnerErrorCode}. 99 | func (e *Error) RFCCode() RFCErrorCode { 100 | return RFCErrorCode(e.ID()) 101 | } 102 | 103 | // ID returns the ID of this error. 104 | func (e *Error) ID() ErrorID { 105 | if e.codeText != "" { 106 | return ErrorID(e.codeText) 107 | } 108 | return ErrorID(strconv.Itoa(int(e.code))) 109 | } 110 | 111 | // Location returns the location where the error is created, 112 | // implements juju/errors locationer interface. 113 | func (e *Error) Location() (file string, line int) { 114 | return e.file, e.line 115 | } 116 | 117 | // MessageTemplate returns the error message template of this error. 118 | func (e *Error) MessageTemplate() string { 119 | return e.message 120 | } 121 | 122 | // Args returns the message arguments of this error. 123 | func (e *Error) Args() []interface{} { 124 | return e.args 125 | } 126 | 127 | // Error implements error interface. 128 | func (e *Error) Error() string { 129 | if e == nil { 130 | return "" 131 | } 132 | if e.cause != nil { 133 | return fmt.Sprintf("[%s]%s: %s", e.RFCCode(), e.GetMsg(), e.cause.Error()) 134 | } 135 | return fmt.Sprintf("[%s]%s", e.RFCCode(), e.GetMsg()) 136 | } 137 | 138 | func (e *Error) GetMsg() string { 139 | if len(e.args) > 0 { 140 | return fmt.Sprintf(e.message, e.args...) 141 | } 142 | return e.message 143 | } 144 | 145 | func (e *Error) GetSelfMsg() string { 146 | return e.GetMsg() 147 | } 148 | 149 | func (e *Error) fillLineAndFile(skip int) { 150 | // skip this 151 | _, file, line, ok := runtime.Caller(skip + 1) 152 | if !ok { 153 | e.file = "" 154 | e.line = -1 155 | return 156 | } 157 | e.file = file 158 | e.line = line 159 | } 160 | 161 | // GenWithStack generates a new *Error with the same class and code, and a new formatted message. 162 | func (e *Error) GenWithStack(format string, args ...interface{}) error { 163 | // TODO: RedactErrorArg 164 | err := *e 165 | err.message = format 166 | err.args = args 167 | err.fillLineAndFile(1) 168 | return AddStack(&err) 169 | } 170 | 171 | // GenWithStackByArgs generates a new *Error with the same class and code, and new arguments. 172 | func (e *Error) GenWithStackByArgs(args ...interface{}) error { 173 | RedactErrorArg(args, e.redactArgsPos) 174 | err := *e 175 | err.args = args 176 | err.fillLineAndFile(1) 177 | return AddStack(&err) 178 | } 179 | 180 | // FastGen generates a new *Error with the same class and code, and a new formatted message. 181 | // This will not call runtime.Caller to get file and line. 182 | func (e *Error) FastGen(format string, args ...interface{}) error { 183 | // TODO: RedactErrorArg 184 | err := *e 185 | err.message = format 186 | err.args = args 187 | return SuspendStack(&err) 188 | } 189 | 190 | // FastGen generates a new *Error with the same class and code, and a new arguments. 191 | // This will not call runtime.Caller to get file and line. 192 | func (e *Error) FastGenByArgs(args ...interface{}) error { 193 | RedactErrorArg(args, e.redactArgsPos) 194 | err := *e 195 | err.args = args 196 | return SuspendStack(&err) 197 | } 198 | 199 | // Equal checks if err is equal to e. 200 | func (e *Error) Equal(err error) bool { 201 | originErr := Cause(err) 202 | if originErr == nil { 203 | return false 204 | } 205 | if error(e) == originErr { 206 | return true 207 | } 208 | inErr, ok := originErr.(*Error) 209 | if !ok { 210 | return false 211 | } 212 | idEquals := e.ID() == inErr.ID() 213 | return idEquals 214 | } 215 | 216 | // NotEqual checks if err is not equal to e. 217 | func (e *Error) NotEqual(err error) bool { 218 | return !e.Equal(err) 219 | } 220 | 221 | // RedactErrorArg redacts the args by position if RedactLogEnabled is enabled. 222 | func RedactErrorArg(args []interface{}, position []int) { 223 | switch RedactLogEnabled.Load() { 224 | case RedactLogEnable: 225 | for _, pos := range position { 226 | if len(args) > pos { 227 | args[pos] = "?" 228 | } 229 | } 230 | case RedactLogMarker: 231 | for _, pos := range position { 232 | if len(args) > pos { 233 | args[pos] = &redactFormatter{args[pos]} 234 | } 235 | } 236 | } 237 | } 238 | 239 | // ErrorEqual returns a boolean indicating whether err1 is equal to err2. 240 | func ErrorEqual(err1, err2 error) bool { 241 | e1 := Cause(err1) 242 | e2 := Cause(err2) 243 | 244 | if e1 == e2 { 245 | return true 246 | } 247 | 248 | if e1 == nil || e2 == nil { 249 | return e1 == e2 250 | } 251 | 252 | te1, ok1 := e1.(*Error) 253 | te2, ok2 := e2.(*Error) 254 | if ok1 && ok2 { 255 | return te1.Equal(te2) 256 | } 257 | 258 | return e1.Error() == e2.Error() 259 | } 260 | 261 | // ErrorNotEqual returns a boolean indicating whether err1 isn't equal to err2. 262 | func ErrorNotEqual(err1, err2 error) bool { 263 | return !ErrorEqual(err1, err2) 264 | } 265 | 266 | type jsonError struct { 267 | // Deprecated field, please use `RFCCode` instead. 268 | Class int `json:"class"` 269 | Code int `json:"code"` 270 | Msg string `json:"message"` 271 | RFCCode string `json:"rfccode"` 272 | } 273 | 274 | func (e *Error) Wrap(err error) *Error { 275 | if err != nil { 276 | newErr := *e 277 | newErr.cause = err 278 | return &newErr 279 | } 280 | return nil 281 | } 282 | 283 | // Unwrap returns cause of the error. 284 | // It allows Error to work with errors.Is() and errors.As() from the Go 285 | // standard package. 286 | func (e *Error) Unwrap() error { 287 | if e == nil { 288 | return nil 289 | } 290 | return e.cause 291 | } 292 | 293 | // Is checks if e has the same error ID with other. 294 | // It allows Error to work with errors.Is() from the Go standard package. 295 | func (e *Error) Is(other error) bool { 296 | err, ok := other.(*Error) 297 | if !ok { 298 | return false 299 | } 300 | return (e == nil && err == nil) || (e != nil && err != nil && e.ID() == err.ID()) 301 | } 302 | 303 | func (e *Error) Cause() error { 304 | root := Unwrap(e.cause) 305 | if root == nil { 306 | return e.cause 307 | } 308 | return root 309 | } 310 | 311 | func (e *Error) FastGenWithCause(args ...interface{}) error { 312 | err := *e 313 | if e.cause != nil { 314 | err.message = e.cause.Error() 315 | } 316 | err.args = args 317 | return SuspendStack(&err) 318 | } 319 | 320 | func (e *Error) GenWithStackByCause(args ...interface{}) error { 321 | err := *e 322 | if e.cause != nil { 323 | err.message = e.cause.Error() 324 | } 325 | err.args = args 326 | err.fillLineAndFile(1) 327 | return AddStack(&err) 328 | } 329 | 330 | type NormalizeOption func(*Error) 331 | 332 | func RedactArgs(pos []int) NormalizeOption { 333 | return func(e *Error) { 334 | e.redactArgsPos = pos 335 | } 336 | } 337 | 338 | // RFCCodeText returns a NormalizeOption to set RFC error code. 339 | func RFCCodeText(codeText string) NormalizeOption { 340 | return func(e *Error) { 341 | e.codeText = ErrCodeText(codeText) 342 | } 343 | } 344 | 345 | // MySQLErrorCode returns a NormalizeOption to set error code. 346 | func MySQLErrorCode(code int) NormalizeOption { 347 | return func(e *Error) { 348 | e.code = ErrCode(code) 349 | } 350 | } 351 | 352 | // Normalize creates a new Error object. 353 | func Normalize(message string, opts ...NormalizeOption) *Error { 354 | e := &Error{ 355 | message: message, 356 | } 357 | for _, opt := range opts { 358 | opt(e) 359 | } 360 | return e 361 | } 362 | 363 | type redactFormatter struct { 364 | arg interface{} 365 | } 366 | 367 | func (e *redactFormatter) Format(f fmt.State, verb rune) { 368 | origin := fmt.Sprintf(fmt.FormatString(f, verb), e.arg) 369 | fmt.Fprintf(f, "‹") 370 | for _, c := range origin { 371 | if c == '‹' || c == '›' { 372 | fmt.Fprintf(f, "%c", c) 373 | fmt.Fprintf(f, "%c", c) 374 | } else { 375 | fmt.Fprintf(f, "%c", c) 376 | } 377 | } 378 | fmt.Fprintf(f, "›") 379 | } 380 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | // Package errors provides simple error handling primitives. 2 | // 3 | // The traditional error handling idiom in Go is roughly akin to 4 | // 5 | // if err != nil { 6 | // return err 7 | // } 8 | // 9 | // which applied recursively up the call stack results in error reports 10 | // without context or debugging information. The errors package allows 11 | // programmers to add context to the failure path in their code in a way 12 | // that does not destroy the original value of the error. 13 | // 14 | // # Adding context to an error 15 | // 16 | // The errors.Annotate function returns a new error that adds context to the 17 | // original error by recording a stack trace at the point Annotate is called, 18 | // and the supplied message. For example 19 | // 20 | // _, err := ioutil.ReadAll(r) 21 | // if err != nil { 22 | // return errors.Annotate(err, "read failed") 23 | // } 24 | // 25 | // If additional control is required the errors.AddStack and errors.WithMessage 26 | // functions destructure errors.Annotate into its component operations of annotating 27 | // an error with a stack trace and an a message, respectively. 28 | // 29 | // # Retrieving the cause of an error 30 | // 31 | // Using errors.Annotate constructs a stack of errors, adding context to the 32 | // preceding error. Depending on the nature of the error it may be necessary 33 | // to reverse the operation of errors.Annotate to retrieve the original error 34 | // for inspection. Any error value which implements this interface 35 | // 36 | // type causer interface { 37 | // Cause() error 38 | // } 39 | // 40 | // can be inspected by errors.Cause. errors.Cause will recursively retrieve 41 | // the topmost error which does not implement causer, which is assumed to be 42 | // the original cause. For example: 43 | // 44 | // switch err := errors.Cause(err).(type) { 45 | // case *MyError: 46 | // // handle specifically 47 | // default: 48 | // // unknown error 49 | // } 50 | // 51 | // causer interface is not exported by this package, but is considered a part 52 | // of stable public API. 53 | // errors.Unwrap is also available: this will retrieve the next error in the chain. 54 | // 55 | // # Formatted printing of errors 56 | // 57 | // All error values returned from this package implement fmt.Formatter and can 58 | // be formatted by the fmt package. The following verbs are supported 59 | // 60 | // %s print the error. If the error has a Cause it will be 61 | // printed recursively 62 | // %v see %s 63 | // %+v extended format. Each Frame of the error's StackTrace will 64 | // be printed in detail. 65 | // 66 | // # Retrieving the stack trace of an error or wrapper 67 | // 68 | // New, Errorf, Annotate, and Annotatef record a stack trace at the point they are invoked. 69 | // This information can be retrieved with the StackTracer interface that returns 70 | // a StackTrace. Where errors.StackTrace is defined as 71 | // 72 | // type StackTrace []Frame 73 | // 74 | // The Frame type represents a call site in the stack trace. Frame supports 75 | // the fmt.Formatter interface that can be used for printing information about 76 | // the stack trace of this error. For example: 77 | // 78 | // if stacked := errors.GetStackTracer(err); stacked != nil { 79 | // for _, f := range stacked.StackTrace() { 80 | // fmt.Printf("%+s:%d\n", f, f) 81 | // } 82 | // } 83 | // 84 | // See the documentation for Frame.Format for more details. 85 | // 86 | // errors.Find can be used to search for an error in the error chain. 87 | package errors 88 | 89 | import ( 90 | "fmt" 91 | "io" 92 | ) 93 | 94 | // represent an error carries with message 95 | type messenger interface { 96 | // GetSelfMsg get its own message, the message of its cause error is NOT included. 97 | GetSelfMsg() string 98 | } 99 | 100 | // New returns an error with the supplied message. 101 | // New also records the stack trace at the point it was called. 102 | func New(message string) error { 103 | return &fundamental{ 104 | msg: message, 105 | stack: callers(), 106 | } 107 | } 108 | 109 | // Errorf formats according to a format specifier and returns the string 110 | // as a value that satisfies error. 111 | // Errorf also records the stack trace at the point it was called. 112 | func Errorf(format string, args ...interface{}) error { 113 | return &fundamental{ 114 | msg: fmt.Sprintf(format, args...), 115 | stack: callers(), 116 | } 117 | } 118 | 119 | // StackTraceAware is an optimization to avoid repetitive traversals of an error chain. 120 | // HasStack checks for this marker first. 121 | // Annotate/Wrap and Annotatef/Wrapf will produce this marker. 122 | type StackTraceAware interface { 123 | HasStack() bool 124 | } 125 | 126 | // HasStack tells whether a StackTracer exists in the error chain 127 | func HasStack(err error) bool { 128 | if errWithStack, ok := err.(StackTraceAware); ok { 129 | return errWithStack.HasStack() 130 | } 131 | // Error.FastGenXXX or call SuspendStack directly will make an empty stack trace, 132 | // which should be considered as no stack trace, to allow upper layer code to 133 | // add stack trace with Trace. 134 | stackTracer := GetStackTracer(err) 135 | return stackTracer != nil && !stackTracer.Empty() 136 | } 137 | 138 | // fundamental is an error that has a message and a stack, but no caller. 139 | type fundamental struct { 140 | msg string 141 | *stack 142 | } 143 | 144 | var _ messenger = (*fundamental)(nil) 145 | 146 | func (f *fundamental) Error() string { return f.msg } 147 | 148 | func (f *fundamental) GetSelfMsg() string { return f.msg } 149 | 150 | func (f *fundamental) Format(s fmt.State, verb rune) { 151 | switch verb { 152 | case 'v': 153 | if s.Flag('+') { 154 | io.WriteString(s, f.msg) 155 | f.stack.Format(s, verb) 156 | return 157 | } 158 | fallthrough 159 | case 's': 160 | io.WriteString(s, f.msg) 161 | case 'q': 162 | fmt.Fprintf(s, "%q", f.msg) 163 | } 164 | } 165 | 166 | // WithStack annotates err with a stack trace at the point WithStack was called. 167 | // If err is nil, WithStack returns nil. 168 | // 169 | // For most use cases this is deprecated and AddStack should be used (which will ensure just one stack trace). 170 | // However, one may want to use this in some situations, for example to create a 2nd trace across a goroutine. 171 | func WithStack(err error) error { 172 | if err == nil { 173 | return nil 174 | } 175 | 176 | return &withStack{ 177 | err, 178 | callers(), 179 | } 180 | } 181 | 182 | // AddStack is similar to WithStack. 183 | // However, it will first check with HasStack to see if a stack trace already exists in the causer chain before creating another one. 184 | func AddStack(err error) error { 185 | if err == nil || HasStack(err) { 186 | return err 187 | } 188 | 189 | return &withStack{ 190 | err, 191 | callers(), 192 | } 193 | } 194 | 195 | type withStack struct { 196 | error 197 | *stack 198 | } 199 | 200 | var _ messenger = (*withStack)(nil) 201 | 202 | func (w *withStack) Cause() error { return w.error } 203 | 204 | func (w *withStack) GetSelfMsg() string { 205 | // it doesn't have its own message, but we still need impl it to avoid calling 206 | // err.Error() for its cause 207 | return "" 208 | } 209 | 210 | // Unwrap provides compatibility for Go 1.13 error chains. 211 | func (w *withStack) Unwrap() error { return w.error } 212 | 213 | func (w *withStack) Format(s fmt.State, verb rune) { 214 | switch verb { 215 | case 'v': 216 | if s.Flag('+') { 217 | fmt.Fprintf(s, "%+v", w.Cause()) 218 | w.stack.Format(s, verb) 219 | return 220 | } 221 | fallthrough 222 | case 's': 223 | io.WriteString(s, w.Error()) 224 | case 'q': 225 | fmt.Fprintf(s, "%q", w.Error()) 226 | } 227 | } 228 | 229 | // Wrap returns an error annotating err with a stack trace 230 | // at the point Wrap is called, and the supplied message. 231 | // If err is nil, Wrap returns nil. 232 | // 233 | // For most use cases this is deprecated in favor of Annotate. 234 | // Annotate avoids creating duplicate stack traces. 235 | func Wrap(err error, message string) error { 236 | if err == nil { 237 | return nil 238 | } 239 | hasStack := HasStack(err) 240 | err = &withMessage{ 241 | cause: err, 242 | msg: message, 243 | causeHasStack: hasStack, 244 | } 245 | return &withStack{ 246 | err, 247 | callers(), 248 | } 249 | } 250 | 251 | // Wrapf returns an error annotating err with a stack trace 252 | // at the point Wrapf is call, and the format specifier. 253 | // If err is nil, Wrapf returns nil. 254 | // 255 | // For most use cases this is deprecated in favor of Annotatef. 256 | // Annotatef avoids creating duplicate stack traces. 257 | func Wrapf(err error, format string, args ...interface{}) error { 258 | if err == nil { 259 | return nil 260 | } 261 | hasStack := HasStack(err) 262 | err = &withMessage{ 263 | cause: err, 264 | msg: fmt.Sprintf(format, args...), 265 | causeHasStack: hasStack, 266 | } 267 | return &withStack{ 268 | err, 269 | callers(), 270 | } 271 | } 272 | 273 | // WithMessage annotates err with a new message. 274 | // If err is nil, WithMessage returns nil. 275 | func WithMessage(err error, message string) error { 276 | if err == nil { 277 | return nil 278 | } 279 | return &withMessage{ 280 | cause: err, 281 | msg: message, 282 | causeHasStack: HasStack(err), 283 | } 284 | } 285 | 286 | type withMessage struct { 287 | cause error 288 | msg string 289 | causeHasStack bool 290 | } 291 | 292 | var _ messenger = (*withMessage)(nil) 293 | 294 | func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } 295 | func (w *withMessage) Cause() error { return w.cause } 296 | 297 | func (w *withMessage) GetSelfMsg() string { return w.msg } 298 | 299 | // Unwrap provides compatibility for Go 1.13 error chains. 300 | func (w *withMessage) Unwrap() error { return w.cause } 301 | func (w *withMessage) HasStack() bool { return w.causeHasStack } 302 | 303 | func (w *withMessage) Format(s fmt.State, verb rune) { 304 | switch verb { 305 | case 'v': 306 | if s.Flag('+') { 307 | fmt.Fprintf(s, "%+v\n", w.Cause()) 308 | io.WriteString(s, w.msg) 309 | return 310 | } 311 | fallthrough 312 | case 's': 313 | io.WriteString(s, w.Error()) 314 | case 'q': 315 | fmt.Fprintf(s, "%q", w.Error()) 316 | } 317 | } 318 | 319 | // Cause returns the underlying cause of the error, if possible. 320 | // An error value has a cause if it implements the following 321 | // interface: 322 | // 323 | // type causer interface { 324 | // Cause() error 325 | // } 326 | // 327 | // If the error does not implement Cause, the original error will 328 | // be returned. If the error is nil, nil will be returned without further 329 | // investigation. 330 | func Cause(err error) error { 331 | cause := Unwrap(err) 332 | if cause == nil { 333 | return err 334 | } 335 | return Cause(cause) 336 | } 337 | 338 | // Unwrap uses causer to return the next error in the chain or nil. 339 | // This goes one-level deeper, whereas Cause goes as far as possible 340 | func Unwrap(err error) error { 341 | type causer interface { 342 | Cause() error 343 | } 344 | if unErr, ok := err.(causer); ok { 345 | return unErr.Cause() 346 | } 347 | return nil 348 | } 349 | 350 | // Find an error in the chain that matches a test function. 351 | // returns nil if no error is found. 352 | func Find(origErr error, test func(error) bool) error { 353 | var foundErr error 354 | WalkDeep(origErr, func(err error) bool { 355 | if test(err) { 356 | foundErr = err 357 | return true 358 | } 359 | return false 360 | }) 361 | return foundErr 362 | } 363 | 364 | // GetErrStackMsg get the concat error message the whole error stack. 365 | // it's different from err.Error(), as pingcap/errors.Error will prepend the error 366 | // code in the result of err.Error(), like below: 367 | // 368 | // [types:1292]Truncated incorrect 369 | // 370 | // and when there are multiple errors.Error in the chain, the err.Error() will 371 | // return like this: 372 | // 373 | // [Lightning:Restore:ErrEncodeKV]encode kv error ... : [types:1292]Truncated incorrect DOUBLE value: 'a'" 374 | // 375 | // But sometimes we only want a single error code with pure message part. 376 | func GetErrStackMsg(err error) string { 377 | if err == nil { 378 | return "" 379 | } 380 | m, ok := err.(messenger) 381 | if ok { 382 | msg := m.GetSelfMsg() 383 | causeMsg := GetErrStackMsg(Unwrap(err)) 384 | if msg == "" { 385 | msg = causeMsg 386 | } else if causeMsg != "" { 387 | msg = msg + ": " + causeMsg 388 | } 389 | return msg 390 | } 391 | return err.Error() 392 | } 393 | -------------------------------------------------------------------------------- /errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net/url" 8 | "reflect" 9 | "strconv" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestNew(t *testing.T) { 16 | tests := []struct { 17 | err string 18 | want error 19 | }{ 20 | {"", fmt.Errorf("")}, 21 | {"foo", fmt.Errorf("foo")}, 22 | {"foo", New("foo")}, 23 | {"string with format specifiers: %v", errors.New("string with format specifiers: %v")}, 24 | } 25 | 26 | for _, tt := range tests { 27 | got := New(tt.err) 28 | if got.Error() != tt.want.Error() { 29 | t.Errorf("New.Error(): got: %q, want %q", got, tt.want) 30 | } 31 | } 32 | } 33 | 34 | func TestWrapNil(t *testing.T) { 35 | got := Annotate(nil, "no error") 36 | if got != nil { 37 | t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got) 38 | } 39 | } 40 | 41 | func TestWrap(t *testing.T) { 42 | tests := []struct { 43 | err error 44 | message string 45 | want string 46 | }{ 47 | {io.EOF, "read error", "read error: EOF"}, 48 | {Annotate(io.EOF, "read error"), "client error", "client error: read error: EOF"}, 49 | } 50 | 51 | for _, tt := range tests { 52 | got := Annotate(tt.err, tt.message).Error() 53 | if got != tt.want { 54 | t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) 55 | } 56 | } 57 | } 58 | 59 | type nilError struct{} 60 | 61 | func (nilError) Error() string { return "nil error" } 62 | 63 | func TestCause(t *testing.T) { 64 | x := New("error") 65 | tests := []struct { 66 | err error 67 | want error 68 | }{{ 69 | // nil error is nil 70 | err: nil, 71 | want: nil, 72 | }, { 73 | // explicit nil error is nil 74 | err: (error)(nil), 75 | want: nil, 76 | }, { 77 | // typed nil is nil 78 | err: (*nilError)(nil), 79 | want: (*nilError)(nil), 80 | }, { 81 | // uncaused error is unaffected 82 | err: io.EOF, 83 | want: io.EOF, 84 | }, { 85 | // caused error returns cause 86 | err: Annotate(io.EOF, "ignored"), 87 | want: io.EOF, 88 | }, { 89 | err: x, // return from errors.New 90 | want: x, 91 | }, { 92 | WithMessage(nil, "whoops"), 93 | nil, 94 | }, { 95 | WithMessage(io.EOF, "whoops"), 96 | io.EOF, 97 | }, { 98 | WithStack(nil), 99 | nil, 100 | }, { 101 | WithStack(io.EOF), 102 | io.EOF, 103 | }, { 104 | AddStack(nil), 105 | nil, 106 | }, { 107 | AddStack(io.EOF), 108 | io.EOF, 109 | }} 110 | 111 | for i, tt := range tests { 112 | got := Cause(tt.err) 113 | if !reflect.DeepEqual(got, tt.want) { 114 | t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want) 115 | } 116 | } 117 | } 118 | 119 | func TestWrapfNil(t *testing.T) { 120 | got := Annotate(nil, "no error") 121 | if got != nil { 122 | t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got) 123 | } 124 | } 125 | 126 | func TestWrapf(t *testing.T) { 127 | tests := []struct { 128 | err error 129 | message string 130 | want string 131 | }{ 132 | {io.EOF, "read error", "read error: EOF"}, 133 | {Annotatef(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, 134 | {Annotatef(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, 135 | } 136 | 137 | for _, tt := range tests { 138 | got := Annotatef(tt.err, tt.message).Error() 139 | if got != tt.want { 140 | t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) 141 | } 142 | } 143 | } 144 | 145 | func TestErrorf(t *testing.T) { 146 | tests := []struct { 147 | err error 148 | want string 149 | }{ 150 | {Errorf("read error without format specifiers"), "read error without format specifiers"}, 151 | {Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"}, 152 | } 153 | 154 | for _, tt := range tests { 155 | got := tt.err.Error() 156 | if got != tt.want { 157 | t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want) 158 | } 159 | } 160 | } 161 | 162 | func TestWithStackNil(t *testing.T) { 163 | got := WithStack(nil) 164 | if got != nil { 165 | t.Errorf("WithStack(nil): got %#v, expected nil", got) 166 | } 167 | got = AddStack(nil) 168 | if got != nil { 169 | t.Errorf("AddStack(nil): got %#v, expected nil", got) 170 | } 171 | } 172 | 173 | func TestWithStack(t *testing.T) { 174 | tests := []struct { 175 | err error 176 | want string 177 | }{ 178 | {io.EOF, "EOF"}, 179 | {WithStack(io.EOF), "EOF"}, 180 | } 181 | 182 | for _, tt := range tests { 183 | got := WithStack(tt.err).Error() 184 | if got != tt.want { 185 | t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want) 186 | } 187 | } 188 | } 189 | 190 | func TestAddStack(t *testing.T) { 191 | tests := []struct { 192 | err error 193 | want string 194 | }{ 195 | {io.EOF, "EOF"}, 196 | {AddStack(io.EOF), "EOF"}, 197 | } 198 | 199 | for _, tt := range tests { 200 | got := AddStack(tt.err).Error() 201 | if got != tt.want { 202 | t.Errorf("AddStack(%v): got: %v, want %v", tt.err, got, tt.want) 203 | } 204 | } 205 | } 206 | 207 | func TestGetStackTracer(t *testing.T) { 208 | orig := io.EOF 209 | if GetStackTracer(orig) != nil { 210 | t.Errorf("GetStackTracer: got: %v, want %v", GetStackTracer(orig), nil) 211 | } 212 | stacked := AddStack(orig) 213 | if GetStackTracer(stacked).(error) != stacked { 214 | t.Errorf("GetStackTracer(stacked): got: %v, want %v", GetStackTracer(stacked), stacked) 215 | } 216 | final := AddStack(stacked) 217 | if GetStackTracer(final).(error) != stacked { 218 | t.Errorf("GetStackTracer(final): got: %v, want %v", GetStackTracer(final), stacked) 219 | } 220 | } 221 | 222 | func TestAddStackDedup(t *testing.T) { 223 | stacked := WithStack(io.EOF) 224 | err := AddStack(stacked) 225 | if err != stacked { 226 | t.Errorf("AddStack: got: %+v, want %+v", err, stacked) 227 | } 228 | err = WithStack(stacked) 229 | if err == stacked { 230 | t.Errorf("WithStack: got: %v, don't want %v", err, stacked) 231 | } 232 | } 233 | 234 | func TestWithMessageNil(t *testing.T) { 235 | got := WithMessage(nil, "no error") 236 | if got != nil { 237 | t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got) 238 | } 239 | } 240 | 241 | func TestWithMessage(t *testing.T) { 242 | tests := []struct { 243 | err error 244 | message string 245 | want string 246 | }{ 247 | {io.EOF, "read error", "read error: EOF"}, 248 | {WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"}, 249 | } 250 | 251 | for _, tt := range tests { 252 | got := WithMessage(tt.err, tt.message).Error() 253 | if got != tt.want { 254 | t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want) 255 | } 256 | } 257 | } 258 | 259 | // errors.New, etc values are not expected to be compared by value 260 | // but the change in errors#27 made them incomparable. Assert that 261 | // various kinds of errors have a functional equality operator, even 262 | // if the result of that equality is always false. 263 | func TestErrorEquality(t *testing.T) { 264 | vals := []error{ 265 | nil, 266 | io.EOF, 267 | errors.New("EOF"), 268 | New("EOF"), 269 | Errorf("EOF"), 270 | Annotate(io.EOF, "EOF"), 271 | Annotatef(io.EOF, "EOF%d", 2), 272 | WithMessage(nil, "whoops"), 273 | WithMessage(io.EOF, "whoops"), 274 | WithStack(io.EOF), 275 | WithStack(nil), 276 | AddStack(io.EOF), 277 | AddStack(nil), 278 | } 279 | 280 | for i := range vals { 281 | for j := range vals { 282 | _ = vals[i] == vals[j] // mustn't panic 283 | } 284 | } 285 | } 286 | 287 | func TestFind(t *testing.T) { 288 | eNew := errors.New("error") 289 | wrapped := Annotate(nilError{}, "nil") 290 | tests := []struct { 291 | err error 292 | finder func(error) bool 293 | found error 294 | }{ 295 | {io.EOF, func(_ error) bool { return true }, io.EOF}, 296 | {io.EOF, func(_ error) bool { return false }, nil}, 297 | {io.EOF, func(err error) bool { return err == io.EOF }, io.EOF}, 298 | {io.EOF, func(err error) bool { return err != io.EOF }, nil}, 299 | 300 | {eNew, func(err error) bool { return true }, eNew}, 301 | {eNew, func(err error) bool { return false }, nil}, 302 | 303 | {nilError{}, func(err error) bool { return true }, nilError{}}, 304 | {nilError{}, func(err error) bool { return false }, nil}, 305 | {nilError{}, func(err error) bool { _, ok := err.(nilError); return ok }, nilError{}}, 306 | 307 | {wrapped, func(err error) bool { return true }, wrapped}, 308 | {wrapped, func(err error) bool { return false }, nil}, 309 | {wrapped, func(err error) bool { _, ok := err.(nilError); return ok }, nilError{}}, 310 | } 311 | 312 | for _, tt := range tests { 313 | got := Find(tt.err, tt.finder) 314 | if got != tt.found { 315 | t.Errorf("WithMessage(%v): got: %q, want %q", tt.err, got, tt.found) 316 | } 317 | } 318 | } 319 | 320 | type errWalkTest struct { 321 | cause error 322 | sub []error 323 | v int 324 | } 325 | 326 | func (e *errWalkTest) Error() string { 327 | return strconv.Itoa(e.v) 328 | } 329 | 330 | func (e *errWalkTest) Cause() error { 331 | return e.cause 332 | } 333 | 334 | func (e *errWalkTest) Errors() []error { 335 | return e.sub 336 | } 337 | 338 | func testFind(err error, v int) bool { 339 | return WalkDeep(err, func(err error) bool { 340 | e := err.(*errWalkTest) 341 | return e.v == v 342 | }) 343 | } 344 | 345 | func TestWalkDeep(t *testing.T) { 346 | err := &errWalkTest{ 347 | sub: []error{ 348 | &errWalkTest{ 349 | v: 10, 350 | cause: &errWalkTest{v: 11}, 351 | }, 352 | &errWalkTest{ 353 | v: 20, 354 | cause: &errWalkTest{v: 21, cause: &errWalkTest{v: 22}}, 355 | }, 356 | &errWalkTest{ 357 | v: 30, 358 | cause: &errWalkTest{v: 31}, 359 | }, 360 | }, 361 | } 362 | 363 | if !testFind(err, 11) { 364 | t.Errorf("not found in first cause chain") 365 | } 366 | 367 | if !testFind(err, 22) { 368 | t.Errorf("not found in siblings") 369 | } 370 | 371 | if testFind(err, 32) { 372 | t.Errorf("found not exists") 373 | } 374 | } 375 | 376 | func TestWalkDeepNil(t *testing.T) { 377 | require.False(t, WalkDeep(nil, func(err error) bool { return true })) 378 | } 379 | 380 | func TestWalkDeepComplexTree(t *testing.T) { 381 | err := &errWalkTest{v: 1, cause: &errWalkTest{ 382 | sub: []error{ 383 | &errWalkTest{ 384 | v: 10, 385 | cause: &errWalkTest{v: 11}, 386 | }, 387 | &errWalkTest{ 388 | v: 20, 389 | sub: []error{ 390 | &errWalkTest{v: 21}, 391 | &errWalkTest{v: 22}, 392 | }, 393 | }, 394 | &errWalkTest{ 395 | v: 30, 396 | cause: &errWalkTest{v: 31}, 397 | }, 398 | }, 399 | }} 400 | 401 | assertFind := func(v int, comment string) { 402 | if !testFind(err, v) { 403 | t.Errorf("%d not found in the error: %s", v, comment) 404 | } 405 | } 406 | assertNotFind := func(v int, comment string) { 407 | if testFind(err, v) { 408 | t.Errorf("%d found in the error, but not expected: %s", v, comment) 409 | } 410 | } 411 | 412 | assertFind(1, "shallow search") 413 | assertFind(11, "deep search A1") 414 | assertFind(21, "deep search A2") 415 | assertFind(22, "deep search B1") 416 | assertNotFind(23, "deep search Neg") 417 | assertFind(31, "deep search B2") 418 | assertNotFind(32, "deep search Neg") 419 | assertFind(30, "Tree node A") 420 | assertFind(20, "Tree node with many children") 421 | } 422 | 423 | type fooError int 424 | 425 | func (fooError) Error() string { 426 | return "foo" 427 | } 428 | 429 | func TestWorkWithStdErrors(t *testing.T) { 430 | e1 := fooError(100) 431 | e2 := Normalize("e2", RFCCodeText("e2")) 432 | e3 := Normalize("e3", RFCCodeText("e3")) 433 | e21 := e2.Wrap(e1) 434 | e31 := e3.Wrap(e1) 435 | e32 := e3.Wrap(e2) 436 | e321 := e3.Wrap(e21) 437 | 438 | unwrapTbl := []struct { 439 | x *Error // x.Unwrap() == y 440 | y error 441 | }{{e2, nil}, {e3, nil}, {e21, e1}, {e31, e1}, {e32, e2}, {e321, e21}} 442 | for _, c := range unwrapTbl { 443 | if c.x.Unwrap() != c.y { 444 | t.Errorf("`%s`.Unwrap() != `%s`", c.x, c.y) 445 | } 446 | } 447 | 448 | isTbl := []struct { 449 | x, y error // errors.Is(x, y) == b 450 | b bool 451 | }{ 452 | {e1, e1, true}, {e2, e1, false}, {e3, e1, false}, {e21, e1, true}, {e321, e1, true}, 453 | {e1, e2, false}, {e2, e2, true}, {e3, e2, false}, {e21, e2, true}, {e31, e2, false}, {e321, e2, true}, 454 | {e2, e21, true}, {e21, e21, true}, {e31, e21, false}, {e321, e21, true}, 455 | {e321, e321, true}, {e3, e321, true}, {e21, e321, false}, 456 | } 457 | for _, c := range isTbl { 458 | if c.b && !errors.Is(c.x, c.y) { 459 | t.Errorf("`%s` is not `%s`", c.x, c.y) 460 | } 461 | if !c.b && errors.Is(c.x, c.y) { 462 | t.Errorf("`%s` is `%s`", c.x, c.y) 463 | } 464 | } 465 | 466 | var e1x fooError 467 | if ok := errors.As(e21, &e1x); !ok { 468 | t.Error("e21 cannot convert to e1") 469 | } 470 | if int(e1x) != 100 { 471 | t.Error("e1x is not 100") 472 | } 473 | 474 | var e2x *Error 475 | if ok := errors.As(e21, &e2x); !ok { 476 | t.Error("e21 cannot convert to e2") 477 | } 478 | if e2x.ID() != "e2" { 479 | t.Error("err is not e2") 480 | } 481 | 482 | e3x := e3.Wrap(e1) 483 | if ok := errors.As(e21, &e3x); !ok { 484 | t.Error("e21 cannot convert to e3") 485 | } 486 | if e3x.ID() != "e2" { 487 | t.Error("err is not e2") 488 | } 489 | } 490 | 491 | func TestHasTrace(t *testing.T) { 492 | targetErr := Normalize("test err") 493 | require.False(t, HasStack(targetErr)) 494 | require.False(t, HasStack(targetErr.FastGen("fast gen"))) 495 | require.False(t, HasStack(targetErr.FastGenByArgs("fast gen arg"))) 496 | require.True(t, HasStack(Trace(targetErr.FastGen("fast gen")))) 497 | require.True(t, HasStack(targetErr.GenWithStack("gen"))) 498 | } 499 | 500 | func TestGetErrStackMsg(t *testing.T) { 501 | require.Equal(t, "", GetErrStackMsg(nil)) 502 | 503 | namedErr := Normalize("named err message", RFCCodeText("NamedError")) 504 | require.False(t, HasStack(namedErr)) 505 | require.Equal(t, "named err message", GetErrStackMsg(namedErr)) 506 | tracedErr := Trace(namedErr) 507 | require.Equal(t, "named err message", GetErrStackMsg(tracedErr)) 508 | 509 | annotatedErr := Annotate(tracedErr, "annotated message") 510 | require.Equal(t, "annotated message: named err message", GetErrStackMsg(annotatedErr)) 511 | 512 | annotatedErr = Annotate(annotatedErr, "annotated message 2") 513 | require.Equal(t, "annotated message 2: annotated message: named err message", GetErrStackMsg(annotatedErr)) 514 | 515 | fundErr := New("new fundamental error") 516 | wrappedErr := namedErr.Wrap(fundErr) 517 | require.Equal(t, "named err message: new fundamental error", GetErrStackMsg(wrappedErr)) 518 | fastGen := wrappedErr.FastGen("fast gen") 519 | require.Equal(t, "fast gen: new fundamental error", GetErrStackMsg(fastGen)) 520 | 521 | urlErr := &url.Error{Op: "GET", URL: "/url", Err: errors.New("internal golang err")} 522 | fastGen = namedErr.Wrap(urlErr).FastGen("fast gen") 523 | require.Equal(t, `fast gen: GET "/url": internal golang err`, GetErrStackMsg(fastGen)) 524 | } 525 | -------------------------------------------------------------------------------- /format_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "regexp" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestFormatNew(t *testing.T) { 13 | tests := []struct { 14 | error 15 | format string 16 | want string 17 | }{{ 18 | New("error"), 19 | "%s", 20 | "error", 21 | }, { 22 | New("error"), 23 | "%v", 24 | "error", 25 | }, { 26 | New("error"), 27 | "%+v", 28 | "error\n" + 29 | "github.com/pingcap/errors.TestFormatNew\n" + 30 | "\t.+/pingcap/errors/format_test.go:26", 31 | }, { 32 | New("error"), 33 | "%q", 34 | `"error"`, 35 | }} 36 | 37 | for i, tt := range tests { 38 | testFormatRegexp(t, i, tt.error, tt.format, tt.want) 39 | } 40 | } 41 | 42 | func TestFormatErrorf(t *testing.T) { 43 | tests := []struct { 44 | error 45 | format string 46 | want string 47 | }{{ 48 | Errorf("%s", "error"), 49 | "%s", 50 | "error", 51 | }, { 52 | Errorf("%s", "error"), 53 | "%v", 54 | "error", 55 | }, { 56 | Errorf("%s", "error"), 57 | "%+v", 58 | "error\n" + 59 | "github.com/pingcap/errors.TestFormatErrorf\n" + 60 | "\t.+/pingcap/errors/format_test.go:56", 61 | }} 62 | 63 | for i, tt := range tests { 64 | testFormatRegexp(t, i, tt.error, tt.format, tt.want) 65 | } 66 | } 67 | 68 | func TestFormatWrap(t *testing.T) { 69 | tests := []struct { 70 | error 71 | format string 72 | want string 73 | }{{ 74 | Annotate(New("error"), "error2"), 75 | "%s", 76 | "error2: error", 77 | }, { 78 | Annotate(New("error"), "error2"), 79 | "%v", 80 | "error2: error", 81 | }, { 82 | Annotate(New("error"), "error2"), 83 | "%+v", 84 | "error\n" + 85 | "github.com/pingcap/errors.TestFormatWrap\n" + 86 | "\t.+/pingcap/errors/format_test.go:82", 87 | }, { 88 | Annotate(io.EOF, "error"), 89 | "%s", 90 | "error: EOF", 91 | }, { 92 | Annotate(io.EOF, "error"), 93 | "%v", 94 | "error: EOF", 95 | }, { 96 | Annotate(io.EOF, "error"), 97 | "%+v", 98 | "EOF\n" + 99 | "error\n" + 100 | "github.com/pingcap/errors.TestFormatWrap\n" + 101 | "\t.+/pingcap/errors/format_test.go:96", 102 | }, { 103 | Annotate(Annotate(io.EOF, "error1"), "error2"), 104 | "%+v", 105 | "EOF\n" + 106 | "error1\n" + 107 | "github.com/pingcap/errors.TestFormatWrap\n" + 108 | "\t.+/pingcap/errors/format_test.go:103\n", 109 | }, { 110 | Annotate(New("error with space"), "context"), 111 | "%q", 112 | `context: error with space`, 113 | }} 114 | 115 | for i, tt := range tests { 116 | testFormatRegexp(t, i, tt.error, tt.format, tt.want) 117 | } 118 | } 119 | 120 | func TestFormatWrapf(t *testing.T) { 121 | tests := []struct { 122 | error 123 | format string 124 | want string 125 | }{{ 126 | Annotatef(io.EOF, "error%d", 2), 127 | "%s", 128 | "error2: EOF", 129 | }, { 130 | Annotatef(io.EOF, "error%d", 2), 131 | "%v", 132 | "error2: EOF", 133 | }, { 134 | Annotatef(io.EOF, "error%d", 2), 135 | "%+v", 136 | "EOF\n" + 137 | "error2\n" + 138 | "github.com/pingcap/errors.TestFormatWrapf\n" + 139 | "\t.+/pingcap/errors/format_test.go:134", 140 | }, { 141 | Annotatef(New("error"), "error%d", 2), 142 | "%s", 143 | "error2: error", 144 | }, { 145 | Annotatef(New("error"), "error%d", 2), 146 | "%v", 147 | "error2: error", 148 | }, { 149 | Annotatef(New("error"), "error%d", 2), 150 | "%+v", 151 | "error\n" + 152 | "github.com/pingcap/errors.TestFormatWrapf\n" + 153 | "\t.+/pingcap/errors/format_test.go:149", 154 | }} 155 | 156 | for i, tt := range tests { 157 | testFormatRegexp(t, i, tt.error, tt.format, tt.want) 158 | } 159 | } 160 | 161 | func TestFormatWithStack(t *testing.T) { 162 | tests := []struct { 163 | error 164 | format string 165 | want []string 166 | }{{ 167 | WithStack(io.EOF), 168 | "%s", 169 | []string{"EOF"}, 170 | }, { 171 | WithStack(io.EOF), 172 | "%v", 173 | []string{"EOF"}, 174 | }, { 175 | WithStack(io.EOF), 176 | "%+v", 177 | []string{"EOF", 178 | "github.com/pingcap/errors.TestFormatWithStack\n" + 179 | "\t.+/pingcap/errors/format_test.go:175"}, 180 | }, { 181 | WithStack(New("error")), 182 | "%s", 183 | []string{"error"}, 184 | }, { 185 | WithStack(New("error")), 186 | "%v", 187 | []string{"error"}, 188 | }, { 189 | WithStack(New("error")), 190 | "%+v", 191 | []string{"error", 192 | "github.com/pingcap/errors.TestFormatWithStack\n" + 193 | "\t.+/pingcap/errors/format_test.go:189", 194 | "github.com/pingcap/errors.TestFormatWithStack\n" + 195 | "\t.+/pingcap/errors/format_test.go:189"}, 196 | }, { 197 | WithStack(WithStack(io.EOF)), 198 | "%+v", 199 | []string{"EOF", 200 | "github.com/pingcap/errors.TestFormatWithStack\n" + 201 | "\t.+/pingcap/errors/format_test.go:197", 202 | "github.com/pingcap/errors.TestFormatWithStack\n" + 203 | "\t.+/pingcap/errors/format_test.go:197"}, 204 | }, { 205 | WithStack(WithStack(Annotatef(io.EOF, "message"))), 206 | "%+v", 207 | []string{"EOF", 208 | "message", 209 | "github.com/pingcap/errors.TestFormatWithStack\n" + 210 | "\t.+/pingcap/errors/format_test.go:205", 211 | "github.com/pingcap/errors.TestFormatWithStack\n" + 212 | "\t.+/pingcap/errors/format_test.go:205", 213 | "github.com/pingcap/errors.TestFormatWithStack\n" + 214 | "\t.+/pingcap/errors/format_test.go:205"}, 215 | }, { 216 | WithStack(Errorf("error%d", 1)), 217 | "%+v", 218 | []string{"error1", 219 | "github.com/pingcap/errors.TestFormatWithStack\n" + 220 | "\t.+/pingcap/errors/format_test.go:216", 221 | "github.com/pingcap/errors.TestFormatWithStack\n" + 222 | "\t.+/pingcap/errors/format_test.go:216"}, 223 | }} 224 | 225 | for i, tt := range tests { 226 | testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) 227 | } 228 | } 229 | 230 | func TestFormatWithMessage(t *testing.T) { 231 | tests := []struct { 232 | error 233 | format string 234 | want []string 235 | }{{ 236 | WithMessage(New("error"), "error2"), 237 | "%s", 238 | []string{"error2: error"}, 239 | }, { 240 | WithMessage(New("error"), "error2"), 241 | "%v", 242 | []string{"error2: error"}, 243 | }, { 244 | WithMessage(New("error"), "error2"), 245 | "%+v", 246 | []string{ 247 | "error", 248 | "github.com/pingcap/errors.TestFormatWithMessage\n" + 249 | "\t.+/pingcap/errors/format_test.go:244", 250 | "error2"}, 251 | }, { 252 | WithMessage(io.EOF, "addition1"), 253 | "%s", 254 | []string{"addition1: EOF"}, 255 | }, { 256 | WithMessage(io.EOF, "addition1"), 257 | "%v", 258 | []string{"addition1: EOF"}, 259 | }, { 260 | WithMessage(io.EOF, "addition1"), 261 | "%+v", 262 | []string{"EOF", "addition1"}, 263 | }, { 264 | WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), 265 | "%v", 266 | []string{"addition2: addition1: EOF"}, 267 | }, { 268 | WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), 269 | "%+v", 270 | []string{"EOF", "addition1", "addition2"}, 271 | }, { 272 | Annotate(WithMessage(io.EOF, "error1"), "error2"), 273 | "%+v", 274 | []string{"EOF", "error1", "error2", 275 | "github.com/pingcap/errors.TestFormatWithMessage\n" + 276 | "\t.+/pingcap/errors/format_test.go:272"}, 277 | }, { 278 | WithMessage(Errorf("error%d", 1), "error2"), 279 | "%+v", 280 | []string{"error1", 281 | "github.com/pingcap/errors.TestFormatWithMessage\n" + 282 | "\t.+/pingcap/errors/format_test.go:278", 283 | "error2"}, 284 | }, { 285 | WithMessage(WithStack(io.EOF), "error"), 286 | "%+v", 287 | []string{ 288 | "EOF", 289 | "github.com/pingcap/errors.TestFormatWithMessage\n" + 290 | "\t.+/pingcap/errors/format_test.go:285", 291 | "error"}, 292 | }, { 293 | WithMessage(Annotate(WithStack(io.EOF), "inside-error"), "outside-error"), 294 | "%+v", 295 | []string{ 296 | "EOF", 297 | "github.com/pingcap/errors.TestFormatWithMessage\n" + 298 | "\t.+/pingcap/errors/format_test.go:293", 299 | "inside-error", 300 | "outside-error"}, 301 | }} 302 | 303 | for i, tt := range tests { 304 | testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) 305 | } 306 | } 307 | 308 | /*func TestFormatGeneric(t *testing.T) { 309 | starts := []struct { 310 | err error 311 | want []string 312 | }{ 313 | {New("new-error"), []string{ 314 | "new-error", 315 | "github.com/pingcap/errors.TestFormatGeneric\n" + 316 | "\t.+/github.com/pingcap/errors/format_test.go:313"}, 317 | }, {Errorf("errorf-error"), []string{ 318 | "errorf-error", 319 | "github.com/pingcap/errors.TestFormatGeneric\n" + 320 | "\t.+/github.com/pingcap/errors/format_test.go:317"}, 321 | }, {errors.New("errors-new-error"), []string{ 322 | "errors-new-error"}, 323 | }, 324 | } 325 | 326 | wrappers := []wrapper{ 327 | { 328 | func(err error) error { return WithMessage(err, "with-message") }, 329 | []string{"with-message"}, 330 | }, { 331 | func(err error) error { return WithStack(err) }, 332 | []string{ 333 | "github.com/pingcap/errors.(func·002|TestFormatGeneric.func2)\n\t" + 334 | ".+/github.com/pingcap/errors/format_test.go:331", 335 | }, 336 | }, { 337 | func(err error) error { return Annotate(err, "wrap-error") }, 338 | []string{ 339 | "wrap-error", 340 | "github.com/pingcap/errors.(func·003|TestFormatGeneric.func3)\n\t" + 341 | ".+/github.com/pingcap/errors/format_test.go:337", 342 | }, 343 | }, { 344 | func(err error) error { return Annotatef(err, "wrapf-error%d", 1) }, 345 | []string{ 346 | "wrapf-error1", 347 | "github.com/pingcap/errors.(func·004|TestFormatGeneric.func4)\n\t" + 348 | ".+/github.com/pingcap/errors/format_test.go:346", 349 | }, 350 | }, 351 | } 352 | 353 | for s := range starts { 354 | err := starts[s].err 355 | want := starts[s].want 356 | testFormatCompleteCompare(t, s, err, "%+v", want, false) 357 | testGenericRecursive(t, err, want, wrappers, 3) 358 | } 359 | }*/ 360 | 361 | func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { 362 | t.Helper() 363 | got := fmt.Sprintf(format, arg) 364 | gotLines := strings.SplitN(got, "\n", -1) 365 | wantLines := strings.SplitN(want, "\n", -1) 366 | 367 | if len(wantLines) > len(gotLines) { 368 | t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want) 369 | return 370 | } 371 | 372 | for i, w := range wantLines { 373 | match, err := regexp.MatchString(w, gotLines[i]) 374 | if err != nil { 375 | t.Fatal(err) 376 | } 377 | if !match { 378 | t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want) 379 | } 380 | } 381 | } 382 | 383 | var stackLineR = regexp.MustCompile(`\.`) 384 | 385 | // parseBlocks parses input into a slice, where: 386 | // - incase entry contains a newline, its a stacktrace 387 | // - incase entry contains no newline, its a solo line. 388 | // 389 | // Detecting stack boundaries only works incase the WithStack-calls are 390 | // to be found on the same line, thats why it is optionally here. 391 | // 392 | // Example use: 393 | // 394 | // for _, e := range blocks { 395 | // if strings.ContainsAny(e, "\n") { 396 | // // Match as stack 397 | // } else { 398 | // // Match as line 399 | // } 400 | // } 401 | func parseBlocks(input string, detectStackboundaries bool) ([]string, error) { 402 | var blocks []string 403 | 404 | stack := "" 405 | wasStack := false 406 | lines := map[string]bool{} // already found lines 407 | 408 | for _, l := range strings.Split(input, "\n") { 409 | isStackLine := stackLineR.MatchString(l) 410 | 411 | switch { 412 | case !isStackLine && wasStack: 413 | blocks = append(blocks, stack, l) 414 | stack = "" 415 | lines = map[string]bool{} 416 | case isStackLine: 417 | if wasStack { 418 | // Detecting two stacks after another, possible cause lines match in 419 | // our tests due to WithStack(WithStack(io.EOF)) on same line. 420 | if detectStackboundaries { 421 | if lines[l] { 422 | if len(stack) == 0 { 423 | return nil, errors.New("len of block must not be zero here") 424 | } 425 | 426 | blocks = append(blocks, stack) 427 | stack = l 428 | lines = map[string]bool{l: true} 429 | continue 430 | } 431 | } 432 | 433 | stack = stack + "\n" + l 434 | } else { 435 | stack = l 436 | } 437 | lines[l] = true 438 | case !isStackLine && !wasStack: 439 | blocks = append(blocks, l) 440 | default: 441 | return nil, errors.New("must not happen") 442 | } 443 | 444 | wasStack = isStackLine 445 | } 446 | 447 | // Use up stack 448 | if stack != "" { 449 | blocks = append(blocks, stack) 450 | } 451 | return blocks, nil 452 | } 453 | 454 | func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) { 455 | gotStr := fmt.Sprintf(format, arg) 456 | 457 | got, err := parseBlocks(gotStr, detectStackBoundaries) 458 | if err != nil { 459 | t.Fatal(err) 460 | } 461 | 462 | if len(got) != len(want) { 463 | t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q", 464 | n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr) 465 | } 466 | 467 | for i := range got { 468 | if strings.ContainsAny(want[i], "\n") { 469 | // Match as stack 470 | match, err := regexp.MatchString(want[i], got[i]) 471 | if err != nil { 472 | t.Fatal(err) 473 | } 474 | if !match { 475 | t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n", 476 | n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want)) 477 | } 478 | } else { 479 | // Match as message 480 | if got[i] != want[i] { 481 | t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i]) 482 | } 483 | } 484 | } 485 | } 486 | 487 | type wrapper struct { 488 | wrap func(err error) error 489 | want []string 490 | } 491 | 492 | func prettyBlocks(blocks []string) string { 493 | var out []string 494 | 495 | for _, b := range blocks { 496 | out = append(out, fmt.Sprintf("%v", b)) 497 | } 498 | 499 | return " " + strings.Join(out, "\n ") 500 | } 501 | 502 | func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) { 503 | if len(beforeWant) == 0 { 504 | panic("beforeWant must not be empty") 505 | } 506 | for _, w := range list { 507 | if len(w.want) == 0 { 508 | panic("want must not be empty") 509 | } 510 | 511 | err := w.wrap(beforeErr) 512 | 513 | // Copy required cause append(beforeWant, ..) modified beforeWant subtly. 514 | beforeCopy := make([]string, len(beforeWant)) 515 | copy(beforeCopy, beforeWant) 516 | 517 | beforeWant := beforeCopy 518 | last := len(beforeWant) - 1 519 | var want []string 520 | 521 | // Merge two stacks behind each other. 522 | if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") { 523 | want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...) 524 | } else { 525 | want = append(beforeWant, w.want...) 526 | } 527 | 528 | testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false) 529 | if maxDepth > 0 { 530 | testGenericRecursive(t, err, want, list, maxDepth-1) 531 | } 532 | } 533 | } 534 | --------------------------------------------------------------------------------