├── LICENSE ├── PATENTS ├── README ├── adaptor.go ├── codereview.cfg ├── doc.go ├── errors.go ├── errors_test.go ├── example_As_test.go ├── example_FormatError_test.go ├── example_test.go ├── fmt.go ├── fmt_test.go ├── fmt_unexported_test.go ├── format.go ├── frame.go ├── go.mod ├── internal └── internal.go ├── stack_test.go ├── wrap.go └── wrap_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This repository holds the transition packages for the new Go 1.13 error values. 2 | See golang.org/design/29934-error-values. 3 | -------------------------------------------------------------------------------- /adaptor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "reflect" 12 | "strconv" 13 | ) 14 | 15 | // FormatError calls the FormatError method of f with an errors.Printer 16 | // configured according to s and verb, and writes the result to s. 17 | func FormatError(f Formatter, s fmt.State, verb rune) { 18 | // Assuming this function is only called from the Format method, and given 19 | // that FormatError takes precedence over Format, it cannot be called from 20 | // any package that supports errors.Formatter. It is therefore safe to 21 | // disregard that State may be a specific printer implementation and use one 22 | // of our choice instead. 23 | 24 | // limitations: does not support printing error as Go struct. 25 | 26 | var ( 27 | sep = " " // separator before next error 28 | p = &state{State: s} 29 | direct = true 30 | ) 31 | 32 | var err error = f 33 | 34 | switch verb { 35 | // Note that this switch must match the preference order 36 | // for ordinary string printing (%#v before %+v, and so on). 37 | 38 | case 'v': 39 | if s.Flag('#') { 40 | if stringer, ok := err.(fmt.GoStringer); ok { 41 | io.WriteString(&p.buf, stringer.GoString()) 42 | goto exit 43 | } 44 | // proceed as if it were %v 45 | } else if s.Flag('+') { 46 | p.printDetail = true 47 | sep = "\n - " 48 | } 49 | case 's': 50 | case 'q', 'x', 'X': 51 | // Use an intermediate buffer in the rare cases that precision, 52 | // truncation, or one of the alternative verbs (q, x, and X) are 53 | // specified. 54 | direct = false 55 | 56 | default: 57 | p.buf.WriteString("%!") 58 | p.buf.WriteRune(verb) 59 | p.buf.WriteByte('(') 60 | switch { 61 | case err != nil: 62 | p.buf.WriteString(reflect.TypeOf(f).String()) 63 | default: 64 | p.buf.WriteString("") 65 | } 66 | p.buf.WriteByte(')') 67 | io.Copy(s, &p.buf) 68 | return 69 | } 70 | 71 | loop: 72 | for { 73 | switch v := err.(type) { 74 | case Formatter: 75 | err = v.FormatError((*printer)(p)) 76 | case fmt.Formatter: 77 | v.Format(p, 'v') 78 | break loop 79 | default: 80 | io.WriteString(&p.buf, v.Error()) 81 | break loop 82 | } 83 | if err == nil { 84 | break 85 | } 86 | if p.needColon || !p.printDetail { 87 | p.buf.WriteByte(':') 88 | p.needColon = false 89 | } 90 | p.buf.WriteString(sep) 91 | p.inDetail = false 92 | p.needNewline = false 93 | } 94 | 95 | exit: 96 | width, okW := s.Width() 97 | prec, okP := s.Precision() 98 | 99 | if !direct || (okW && width > 0) || okP { 100 | // Construct format string from State s. 101 | format := []byte{'%'} 102 | if s.Flag('-') { 103 | format = append(format, '-') 104 | } 105 | if s.Flag('+') { 106 | format = append(format, '+') 107 | } 108 | if s.Flag(' ') { 109 | format = append(format, ' ') 110 | } 111 | if okW { 112 | format = strconv.AppendInt(format, int64(width), 10) 113 | } 114 | if okP { 115 | format = append(format, '.') 116 | format = strconv.AppendInt(format, int64(prec), 10) 117 | } 118 | format = append(format, string(verb)...) 119 | fmt.Fprintf(s, string(format), p.buf.String()) 120 | } else { 121 | io.Copy(s, &p.buf) 122 | } 123 | } 124 | 125 | var detailSep = []byte("\n ") 126 | 127 | // state tracks error printing state. It implements fmt.State. 128 | type state struct { 129 | fmt.State 130 | buf bytes.Buffer 131 | 132 | printDetail bool 133 | inDetail bool 134 | needColon bool 135 | needNewline bool 136 | } 137 | 138 | func (s *state) Write(b []byte) (n int, err error) { 139 | if s.printDetail { 140 | if len(b) == 0 { 141 | return 0, nil 142 | } 143 | if s.inDetail && s.needColon { 144 | s.needNewline = true 145 | if b[0] == '\n' { 146 | b = b[1:] 147 | } 148 | } 149 | k := 0 150 | for i, c := range b { 151 | if s.needNewline { 152 | if s.inDetail && s.needColon { 153 | s.buf.WriteByte(':') 154 | s.needColon = false 155 | } 156 | s.buf.Write(detailSep) 157 | s.needNewline = false 158 | } 159 | if c == '\n' { 160 | s.buf.Write(b[k:i]) 161 | k = i + 1 162 | s.needNewline = true 163 | } 164 | } 165 | s.buf.Write(b[k:]) 166 | if !s.inDetail { 167 | s.needColon = true 168 | } 169 | } else if !s.inDetail { 170 | s.buf.Write(b) 171 | } 172 | return len(b), nil 173 | } 174 | 175 | // printer wraps a state to implement an xerrors.Printer. 176 | type printer state 177 | 178 | func (s *printer) Print(args ...any) { 179 | if !s.inDetail || s.printDetail { 180 | fmt.Fprint((*state)(s), args...) 181 | } 182 | } 183 | 184 | func (s *printer) Printf(format string, args ...any) { 185 | if !s.inDetail || s.printDetail { 186 | fmt.Fprintf((*state)(s), format, args...) 187 | } 188 | } 189 | 190 | func (s *printer) Detail() bool { 191 | s.inDetail = true 192 | return s.printDetail 193 | } 194 | -------------------------------------------------------------------------------- /codereview.cfg: -------------------------------------------------------------------------------- 1 | issuerepo: golang/go 2 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package xerrors implements functions to manipulate errors. 6 | // 7 | // This package is based on the Go 2 proposal for error values: 8 | // 9 | // https://go.dev/design/29934-error-values 10 | // 11 | // These functions were incorporated into the standard library's errors package 12 | // in Go 1.13: 13 | // - Is 14 | // - As 15 | // - Unwrap 16 | // 17 | // Also, Errorf's %w verb was incorporated into fmt.Errorf. 18 | // 19 | // No other features of this package were included in Go 1.13, and at present 20 | // there are no plans to include any of them. 21 | package xerrors 22 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors 6 | 7 | import "fmt" 8 | 9 | // errorString is a trivial implementation of error. 10 | type errorString struct { 11 | s string 12 | frame Frame 13 | } 14 | 15 | // New returns an error that formats as the given text. 16 | // 17 | // The returned error contains a Frame set to the caller's location and 18 | // implements Formatter to show this information when printed with details. 19 | func New(text string) error { 20 | return &errorString{text, Caller(1)} 21 | } 22 | 23 | func (e *errorString) Error() string { 24 | return e.s 25 | } 26 | 27 | func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) } 28 | 29 | func (e *errorString) FormatError(p Printer) (next error) { 30 | p.Print(e.s) 31 | e.frame.Format(p) 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors_test 6 | 7 | import ( 8 | "fmt" 9 | "regexp" 10 | "testing" 11 | 12 | "golang.org/x/xerrors" 13 | ) 14 | 15 | func TestNewEqual(t *testing.T) { 16 | // Different allocations should not be equal. 17 | if xerrors.New("abc") == xerrors.New("abc") { 18 | t.Errorf(`New("abc") == New("abc")`) 19 | } 20 | if xerrors.New("abc") == xerrors.New("xyz") { 21 | t.Errorf(`New("abc") == New("xyz")`) 22 | } 23 | 24 | // Same allocation should be equal to itself (not crash). 25 | err := xerrors.New("jkl") 26 | if err != err { 27 | t.Errorf(`err != err`) 28 | } 29 | } 30 | 31 | func TestErrorMethod(t *testing.T) { 32 | err := xerrors.New("abc") 33 | if err.Error() != "abc" { 34 | t.Errorf(`New("abc").Error() = %q, want %q`, err.Error(), "abc") 35 | } 36 | } 37 | 38 | func TestNewDetail(t *testing.T) { 39 | got := fmt.Sprintf("%+v", xerrors.New("error")) 40 | want := `(?s)error:.+errors_test.go:\d+` 41 | ok, err := regexp.MatchString(want, got) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if !ok { 46 | t.Errorf(`fmt.Sprintf("%%+v", New("error")) = %q, want %q"`, got, want) 47 | } 48 | } 49 | 50 | func ExampleNew() { 51 | err := xerrors.New("emit macho dwarf: elf header corrupted") 52 | if err != nil { 53 | fmt.Print(err) 54 | } 55 | // Output: emit macho dwarf: elf header corrupted 56 | } 57 | 58 | // The fmt package's Errorf function lets us use the package's formatting 59 | // features to create descriptive error messages. 60 | func ExampleNew_errorf() { 61 | const name, id = "bimmler", 17 62 | err := fmt.Errorf("user %q (id %d) not found", name, id) 63 | if err != nil { 64 | fmt.Print(err) 65 | } 66 | // Output: user "bimmler" (id 17) not found 67 | } 68 | -------------------------------------------------------------------------------- /example_As_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors_test 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "golang.org/x/xerrors" 12 | ) 13 | 14 | func ExampleAs() { 15 | _, err := os.Open("non-existing") 16 | if err != nil { 17 | var pathError *os.PathError 18 | if xerrors.As(err, &pathError) { 19 | fmt.Println("Failed at path:", pathError.Path) 20 | } 21 | } 22 | 23 | // Output: 24 | // Failed at path: non-existing 25 | } 26 | -------------------------------------------------------------------------------- /example_FormatError_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors_test 6 | 7 | import ( 8 | "fmt" 9 | 10 | "golang.org/x/xerrors" 11 | ) 12 | 13 | type MyError2 struct { 14 | Message string 15 | frame xerrors.Frame 16 | } 17 | 18 | func (m *MyError2) Error() string { 19 | return m.Message 20 | } 21 | 22 | func (m *MyError2) Format(f fmt.State, c rune) { // implements fmt.Formatter 23 | xerrors.FormatError(m, f, c) 24 | } 25 | 26 | func (m *MyError2) FormatError(p xerrors.Printer) error { // implements xerrors.Formatter 27 | p.Print(m.Message) 28 | if p.Detail() { 29 | m.frame.Format(p) 30 | } 31 | return nil 32 | } 33 | 34 | func ExampleFormatError() { 35 | err := &MyError2{Message: "oops", frame: xerrors.Caller(1)} 36 | fmt.Printf("%v\n", err) 37 | fmt.Println() 38 | fmt.Printf("%+v\n", err) 39 | } 40 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors_test 6 | 7 | import ( 8 | "fmt" 9 | "time" 10 | ) 11 | 12 | // MyError is an error implementation that includes a time and message. 13 | type MyError struct { 14 | When time.Time 15 | What string 16 | } 17 | 18 | func (e MyError) Error() string { 19 | return fmt.Sprintf("%v: %v", e.When, e.What) 20 | } 21 | 22 | func oops() error { 23 | return MyError{ 24 | time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC), 25 | "the file system has gone away", 26 | } 27 | } 28 | 29 | func Example() { 30 | if err := oops(); err != nil { 31 | fmt.Println(err) 32 | } 33 | // Output: 1989-03-15 22:30:00 +0000 UTC: the file system has gone away 34 | } 35 | -------------------------------------------------------------------------------- /fmt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | "unicode" 11 | "unicode/utf8" 12 | 13 | "golang.org/x/xerrors/internal" 14 | ) 15 | 16 | const percentBangString = "%!" 17 | 18 | // Errorf formats according to a format specifier and returns the string as a 19 | // value that satisfies error. 20 | // 21 | // The returned error includes the file and line number of the caller when 22 | // formatted with additional detail enabled. If the last argument is an error 23 | // the returned error's Format method will return it if the format string ends 24 | // with ": %s", ": %v", or ": %w". If the last argument is an error and the 25 | // format string ends with ": %w", the returned error implements an Unwrap 26 | // method returning it. 27 | // 28 | // If the format specifier includes a %w verb with an error operand in a 29 | // position other than at the end, the returned error will still implement an 30 | // Unwrap method returning the operand, but the error's Format method will not 31 | // return the wrapped error. 32 | // 33 | // It is invalid to include more than one %w verb or to supply it with an 34 | // operand that does not implement the error interface. The %w verb is otherwise 35 | // a synonym for %v. 36 | // 37 | // Note that as of Go 1.13, the fmt.Errorf function will do error formatting, 38 | // but it will not capture a stack backtrace. 39 | func Errorf(format string, a ...any) error { 40 | format = formatPlusW(format) 41 | // Support a ": %[wsv]" suffix, which works well with xerrors.Formatter. 42 | wrap := strings.HasSuffix(format, ": %w") 43 | idx, format2, ok := parsePercentW(format) 44 | percentWElsewhere := !wrap && idx >= 0 45 | if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) { 46 | err := errorAt(a, len(a)-1) 47 | if err == nil { 48 | return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)} 49 | } 50 | // TODO: this is not entirely correct. The error value could be 51 | // printed elsewhere in format if it mixes numbered with unnumbered 52 | // substitutions. With relatively small changes to doPrintf we can 53 | // have it optionally ignore extra arguments and pass the argument 54 | // list in its entirety. 55 | msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...) 56 | frame := Frame{} 57 | if internal.EnableTrace { 58 | frame = Caller(1) 59 | } 60 | if wrap { 61 | return &wrapError{msg, err, frame} 62 | } 63 | return &noWrapError{msg, err, frame} 64 | } 65 | // Support %w anywhere. 66 | // TODO: don't repeat the wrapped error's message when %w occurs in the middle. 67 | msg := fmt.Sprintf(format2, a...) 68 | if idx < 0 { 69 | return &noWrapError{msg, nil, Caller(1)} 70 | } 71 | err := errorAt(a, idx) 72 | if !ok || err == nil { 73 | // Too many %ws or argument of %w is not an error. Approximate the Go 74 | // 1.13 fmt.Errorf message. 75 | return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)} 76 | } 77 | frame := Frame{} 78 | if internal.EnableTrace { 79 | frame = Caller(1) 80 | } 81 | return &wrapError{msg, err, frame} 82 | } 83 | 84 | func errorAt(args []any, i int) error { 85 | if i < 0 || i >= len(args) { 86 | return nil 87 | } 88 | err, ok := args[i].(error) 89 | if !ok { 90 | return nil 91 | } 92 | return err 93 | } 94 | 95 | // formatPlusW is used to avoid the vet check that will barf at %w. 96 | func formatPlusW(s string) string { 97 | return s 98 | } 99 | 100 | // Return the index of the only %w in format, or -1 if none. 101 | // Also return a rewritten format string with %w replaced by %v, and 102 | // false if there is more than one %w. 103 | // TODO: handle "%[N]w". 104 | func parsePercentW(format string) (idx int, newFormat string, ok bool) { 105 | // Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go. 106 | idx = -1 107 | ok = true 108 | n := 0 109 | sz := 0 110 | var isW bool 111 | for i := 0; i < len(format); i += sz { 112 | if format[i] != '%' { 113 | sz = 1 114 | continue 115 | } 116 | // "%%" is not a format directive. 117 | if i+1 < len(format) && format[i+1] == '%' { 118 | sz = 2 119 | continue 120 | } 121 | sz, isW = parsePrintfVerb(format[i:]) 122 | if isW { 123 | if idx >= 0 { 124 | ok = false 125 | } else { 126 | idx = n 127 | } 128 | // "Replace" the last character, the 'w', with a 'v'. 129 | p := i + sz - 1 130 | format = format[:p] + "v" + format[p+1:] 131 | } 132 | n++ 133 | } 134 | return idx, format, ok 135 | } 136 | 137 | // Parse the printf verb starting with a % at s[0]. 138 | // Return how many bytes it occupies and whether the verb is 'w'. 139 | func parsePrintfVerb(s string) (int, bool) { 140 | // Assume only that the directive is a sequence of non-letters followed by a single letter. 141 | sz := 0 142 | var r rune 143 | for i := 1; i < len(s); i += sz { 144 | r, sz = utf8.DecodeRuneInString(s[i:]) 145 | if unicode.IsLetter(r) { 146 | return i + sz, r == 'w' 147 | } 148 | } 149 | return len(s), false 150 | } 151 | 152 | type noWrapError struct { 153 | msg string 154 | err error 155 | frame Frame 156 | } 157 | 158 | func (e *noWrapError) Error() string { 159 | return fmt.Sprint(e) 160 | } 161 | 162 | func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } 163 | 164 | func (e *noWrapError) FormatError(p Printer) (next error) { 165 | p.Print(e.msg) 166 | e.frame.Format(p) 167 | return e.err 168 | } 169 | 170 | type wrapError struct { 171 | msg string 172 | err error 173 | frame Frame 174 | } 175 | 176 | func (e *wrapError) Error() string { 177 | return fmt.Sprint(e) 178 | } 179 | 180 | func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } 181 | 182 | func (e *wrapError) FormatError(p Printer) (next error) { 183 | p.Print(e.msg) 184 | e.frame.Format(p) 185 | return e.err 186 | } 187 | 188 | func (e *wrapError) Unwrap() error { 189 | return e.err 190 | } 191 | -------------------------------------------------------------------------------- /fmt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors_test 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "os" 11 | "path" 12 | "reflect" 13 | "regexp" 14 | "strconv" 15 | "strings" 16 | "testing" 17 | 18 | "golang.org/x/xerrors" 19 | ) 20 | 21 | func TestErrorf(t *testing.T) { 22 | chained := &wrapped{"chained", nil} 23 | chain := func(s ...string) (a []string) { 24 | for _, s := range s { 25 | a = append(a, cleanPath(s)) 26 | } 27 | return a 28 | } 29 | testCases := []struct { 30 | got error 31 | want []string 32 | }{{ 33 | xerrors.Errorf("no args"), 34 | chain("no args/path.TestErrorf/path.go:xxx"), 35 | }, { 36 | xerrors.Errorf("no args: %s"), 37 | chain("no args: %!s(MISSING)/path.TestErrorf/path.go:xxx"), 38 | }, { 39 | xerrors.Errorf("nounwrap: %s", "simple"), 40 | chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`), 41 | }, { 42 | xerrors.Errorf("nounwrap: %v", "simple"), 43 | chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`), 44 | }, { 45 | xerrors.Errorf("%s failed: %v", "foo", chained), 46 | chain("foo failed/path.TestErrorf/path.go:xxx", 47 | "chained/somefile.go:xxx"), 48 | }, { 49 | xerrors.Errorf("no wrap: %s", chained), 50 | chain("no wrap/path.TestErrorf/path.go:xxx", 51 | "chained/somefile.go:xxx"), 52 | }, { 53 | xerrors.Errorf("%s failed: %w", "foo", chained), 54 | chain("wraps:foo failed/path.TestErrorf/path.go:xxx", 55 | "chained/somefile.go:xxx"), 56 | }, { 57 | xerrors.Errorf("nowrapv: %v", chained), 58 | chain("nowrapv/path.TestErrorf/path.go:xxx", 59 | "chained/somefile.go:xxx"), 60 | }, { 61 | xerrors.Errorf("wrapw: %w", chained), 62 | chain("wraps:wrapw/path.TestErrorf/path.go:xxx", 63 | "chained/somefile.go:xxx"), 64 | }, { 65 | xerrors.Errorf("wrapw %w middle", chained), 66 | chain("wraps:wrapw chained middle/path.TestErrorf/path.go:xxx", 67 | "chained/somefile.go:xxx"), 68 | }, { 69 | xerrors.Errorf("not wrapped: %+v", chained), 70 | chain("not wrapped: chained: somefile.go:123/path.TestErrorf/path.go:xxx"), 71 | }} 72 | for i, tc := range testCases { 73 | t.Run(strconv.Itoa(i)+"/"+path.Join(tc.want...), func(t *testing.T) { 74 | got := errToParts(tc.got) 75 | if !reflect.DeepEqual(got, tc.want) { 76 | t.Errorf("Format:\n got: %#v\nwant: %#v", got, tc.want) 77 | } 78 | 79 | gotStr := tc.got.Error() 80 | wantStr := fmt.Sprint(tc.got) 81 | if gotStr != wantStr { 82 | t.Errorf("Error:\n got: %#v\nwant: %#v", got, tc.want) 83 | } 84 | }) 85 | } 86 | } 87 | 88 | func TestErrorFormatter(t *testing.T) { 89 | var ( 90 | simple = &wrapped{"simple", nil} 91 | elephant = &wrapped{ 92 | "can't adumbrate elephant", 93 | detailed{}, 94 | } 95 | nonascii = &wrapped{"café", nil} 96 | newline = &wrapped{"msg with\nnewline", 97 | &wrapped{"and another\none", nil}} 98 | fallback = &wrapped{"fallback", os.ErrNotExist} 99 | oldAndNew = &wrapped{"new style", formatError("old style")} 100 | framed = &withFrameAndMore{ 101 | frame: xerrors.Caller(0), 102 | } 103 | opaque = &wrapped{"outer", 104 | xerrors.Opaque(&wrapped{"mid", 105 | &wrapped{"inner", nil}})} 106 | ) 107 | testCases := []struct { 108 | err error 109 | fmt string 110 | want string 111 | regexp bool 112 | }{{ 113 | err: simple, 114 | fmt: "%s", 115 | want: "simple", 116 | }, { 117 | err: elephant, 118 | fmt: "%s", 119 | want: "can't adumbrate elephant: out of peanuts", 120 | }, { 121 | err: &wrapped{"a", &wrapped{"b", &wrapped{"c", nil}}}, 122 | fmt: "%s", 123 | want: "a: b: c", 124 | }, { 125 | err: simple, 126 | fmt: "%+v", 127 | want: "simple:" + 128 | "\n somefile.go:123", 129 | }, { 130 | err: elephant, 131 | fmt: "%+v", 132 | want: "can't adumbrate elephant:" + 133 | "\n somefile.go:123" + 134 | "\n - out of peanuts:" + 135 | "\n the elephant is on strike" + 136 | "\n and the 12 monkeys" + 137 | "\n are laughing", 138 | }, { 139 | err: &oneNewline{nil}, 140 | fmt: "%+v", 141 | want: "123", 142 | }, { 143 | err: &oneNewline{&oneNewline{nil}}, 144 | fmt: "%+v", 145 | want: "123:" + 146 | "\n - 123", 147 | }, { 148 | err: &newlineAtEnd{nil}, 149 | fmt: "%+v", 150 | want: "newlineAtEnd:\n detail", 151 | }, { 152 | err: &newlineAtEnd{&newlineAtEnd{nil}}, 153 | fmt: "%+v", 154 | want: "newlineAtEnd:" + 155 | "\n detail" + 156 | "\n - newlineAtEnd:" + 157 | "\n detail", 158 | }, { 159 | err: framed, 160 | fmt: "%+v", 161 | want: "something:" + 162 | "\n golang.org/x/xerrors_test.TestErrorFormatter" + 163 | "\n .+/fmt_test.go:101" + 164 | "\n something more", 165 | regexp: true, 166 | }, { 167 | err: fmtTwice("Hello World!"), 168 | fmt: "%#v", 169 | want: "2 times Hello World!", 170 | }, { 171 | err: fallback, 172 | fmt: "%s", 173 | want: "fallback: file does not exist", 174 | }, { 175 | err: fallback, 176 | fmt: "%+v", 177 | // Note: no colon after the last error, as there are no details. 178 | want: "fallback:" + 179 | "\n somefile.go:123" + 180 | "\n - file does not exist", 181 | }, { 182 | err: opaque, 183 | fmt: "%s", 184 | want: "outer: mid: inner", 185 | }, { 186 | err: opaque, 187 | fmt: "%+v", 188 | want: "outer:" + 189 | "\n somefile.go:123" + 190 | "\n - mid:" + 191 | "\n somefile.go:123" + 192 | "\n - inner:" + 193 | "\n somefile.go:123", 194 | }, { 195 | err: oldAndNew, 196 | fmt: "%v", 197 | want: "new style: old style", 198 | }, { 199 | err: oldAndNew, 200 | fmt: "%q", 201 | want: `"new style: old style"`, 202 | }, { 203 | err: oldAndNew, 204 | fmt: "%+v", 205 | // Note the extra indentation. 206 | // Colon for old style error is rendered by the fmt.Formatter 207 | // implementation of the old-style error. 208 | want: "new style:" + 209 | "\n somefile.go:123" + 210 | "\n - old style:" + 211 | "\n otherfile.go:456", 212 | }, { 213 | err: simple, 214 | fmt: "%-12s", 215 | want: "simple ", 216 | }, { 217 | // Don't use formatting flags for detailed view. 218 | err: simple, 219 | fmt: "%+12v", 220 | want: "simple:" + 221 | "\n somefile.go:123", 222 | }, { 223 | err: elephant, 224 | fmt: "%+50s", 225 | want: " can't adumbrate elephant: out of peanuts", 226 | }, { 227 | err: nonascii, 228 | fmt: "%q", 229 | want: `"café"`, 230 | }, { 231 | err: nonascii, 232 | fmt: "%+q", 233 | want: `"caf\u00e9"`, 234 | }, { 235 | err: simple, 236 | fmt: "% x", 237 | want: "73 69 6d 70 6c 65", 238 | }, { 239 | err: newline, 240 | fmt: "%s", 241 | want: "msg with" + 242 | "\nnewline: and another" + 243 | "\none", 244 | }, { 245 | err: newline, 246 | fmt: "%+v", 247 | want: "msg with" + 248 | "\n newline:" + 249 | "\n somefile.go:123" + 250 | "\n - and another" + 251 | "\n one:" + 252 | "\n somefile.go:123", 253 | }, { 254 | err: &wrapped{"", &wrapped{"inner message", nil}}, 255 | fmt: "%+v", 256 | want: "somefile.go:123" + 257 | "\n - inner message:" + 258 | "\n somefile.go:123", 259 | }, { 260 | err: spurious(""), 261 | fmt: "%s", 262 | want: "spurious", 263 | }, { 264 | err: spurious(""), 265 | fmt: "%+v", 266 | want: "spurious", 267 | }, { 268 | err: spurious("extra"), 269 | fmt: "%s", 270 | want: "spurious", 271 | }, { 272 | err: spurious("extra"), 273 | fmt: "%+v", 274 | want: "spurious:\n" + 275 | " extra", 276 | }, { 277 | err: nil, 278 | fmt: "%+v", 279 | want: "", 280 | }, { 281 | err: (*wrapped)(nil), 282 | fmt: "%+v", 283 | want: "", 284 | }, { 285 | err: simple, 286 | fmt: "%T", 287 | want: "*xerrors_test.wrapped", 288 | }, { 289 | err: simple, 290 | fmt: "%🤪", 291 | want: "%!🤪(*xerrors_test.wrapped)", 292 | // For 1.13: 293 | // want: "%!🤪(*xerrors_test.wrapped=&{simple })", 294 | }, { 295 | err: formatError("use fmt.Formatter"), 296 | fmt: "%#v", 297 | want: "use fmt.Formatter", 298 | }, { 299 | err: fmtTwice("%s %s", "ok", panicValue{}), 300 | fmt: "%s", 301 | // Different Go versions produce different results. 302 | want: `ok %!s\(PANIC=(String method: )?panic\)/ok %!s\(PANIC=(String method: )?panic\)`, 303 | regexp: true, 304 | }, { 305 | err: fmtTwice("%o %s", panicValue{}, "ok"), 306 | fmt: "%s", 307 | want: "{} ok/{} ok", 308 | }, { 309 | err: adapted{"adapted", nil}, 310 | fmt: "%+v", 311 | want: "adapted:" + 312 | "\n detail", 313 | }, { 314 | err: adapted{"outer", adapted{"mid", adapted{"inner", nil}}}, 315 | fmt: "%+v", 316 | want: "outer:" + 317 | "\n detail" + 318 | "\n - mid:" + 319 | "\n detail" + 320 | "\n - inner:" + 321 | "\n detail", 322 | }} 323 | for i, tc := range testCases { 324 | t.Run(fmt.Sprintf("%d/%s", i, tc.fmt), func(t *testing.T) { 325 | got := fmt.Sprintf(tc.fmt, tc.err) 326 | var ok bool 327 | if tc.regexp { 328 | var err error 329 | ok, err = regexp.MatchString(tc.want+"$", got) 330 | if err != nil { 331 | t.Fatal(err) 332 | } 333 | } else { 334 | ok = got == tc.want 335 | } 336 | if !ok { 337 | t.Errorf("\n got: %q\nwant: %q", got, tc.want) 338 | } 339 | }) 340 | } 341 | } 342 | 343 | func TestAdaptor(t *testing.T) { 344 | testCases := []struct { 345 | err error 346 | fmt string 347 | want string 348 | regexp bool 349 | }{{ 350 | err: adapted{"adapted", nil}, 351 | fmt: "%+v", 352 | want: "adapted:" + 353 | "\n detail", 354 | }, { 355 | err: adapted{"outer", adapted{"mid", adapted{"inner", nil}}}, 356 | fmt: "%+v", 357 | want: "outer:" + 358 | "\n detail" + 359 | "\n - mid:" + 360 | "\n detail" + 361 | "\n - inner:" + 362 | "\n detail", 363 | }} 364 | for i, tc := range testCases { 365 | t.Run(fmt.Sprintf("%d/%s", i, tc.fmt), func(t *testing.T) { 366 | got := fmt.Sprintf(tc.fmt, tc.err) 367 | if got != tc.want { 368 | t.Errorf("\n got: %q\nwant: %q", got, tc.want) 369 | } 370 | }) 371 | } 372 | } 373 | 374 | var _ xerrors.Formatter = wrapped{} 375 | 376 | type wrapped struct { 377 | msg string 378 | err error 379 | } 380 | 381 | func (e wrapped) Error() string { return "should call Format" } 382 | 383 | func (e wrapped) Format(s fmt.State, verb rune) { 384 | xerrors.FormatError(&e, s, verb) 385 | } 386 | 387 | func (e wrapped) FormatError(p xerrors.Printer) (next error) { 388 | p.Print(e.msg) 389 | p.Detail() 390 | p.Print("somefile.go:123") 391 | return e.err 392 | } 393 | 394 | var _ xerrors.Formatter = detailed{} 395 | 396 | type detailed struct{} 397 | 398 | func (e detailed) Error() string { panic("should have called FormatError") } 399 | 400 | func (detailed) FormatError(p xerrors.Printer) (next error) { 401 | p.Printf("out of %s", "peanuts") 402 | p.Detail() 403 | p.Print("the elephant is on strike\n") 404 | p.Printf("and the %d monkeys\nare laughing", 12) 405 | return nil 406 | } 407 | 408 | type withFrameAndMore struct { 409 | frame xerrors.Frame 410 | } 411 | 412 | func (e *withFrameAndMore) Error() string { return fmt.Sprint(e) } 413 | 414 | func (e *withFrameAndMore) Format(s fmt.State, v rune) { 415 | xerrors.FormatError(e, s, v) 416 | } 417 | 418 | func (e *withFrameAndMore) FormatError(p xerrors.Printer) (next error) { 419 | p.Print("something") 420 | if p.Detail() { 421 | e.frame.Format(p) 422 | p.Print("something more") 423 | } 424 | return nil 425 | } 426 | 427 | type spurious string 428 | 429 | func (e spurious) Error() string { return fmt.Sprint(e) } 430 | 431 | // move to 1_12 test file 432 | func (e spurious) Format(s fmt.State, verb rune) { 433 | xerrors.FormatError(e, s, verb) 434 | } 435 | 436 | func (e spurious) FormatError(p xerrors.Printer) (next error) { 437 | p.Print("spurious") 438 | p.Detail() // Call detail even if we don't print anything 439 | if e == "" { 440 | p.Print() 441 | } else { 442 | p.Print("\n", string(e)) // print extraneous leading newline 443 | } 444 | return nil 445 | } 446 | 447 | type oneNewline struct { 448 | next error 449 | } 450 | 451 | func (e *oneNewline) Error() string { return fmt.Sprint(e) } 452 | 453 | func (e *oneNewline) Format(s fmt.State, verb rune) { 454 | xerrors.FormatError(e, s, verb) 455 | } 456 | 457 | func (e *oneNewline) FormatError(p xerrors.Printer) (next error) { 458 | p.Print("1") 459 | p.Print("2") 460 | p.Print("3") 461 | p.Detail() 462 | p.Print("\n") 463 | return e.next 464 | } 465 | 466 | type newlineAtEnd struct { 467 | next error 468 | } 469 | 470 | func (e *newlineAtEnd) Error() string { return fmt.Sprint(e) } 471 | 472 | func (e *newlineAtEnd) Format(s fmt.State, verb rune) { 473 | xerrors.FormatError(e, s, verb) 474 | } 475 | 476 | func (e *newlineAtEnd) FormatError(p xerrors.Printer) (next error) { 477 | p.Print("newlineAtEnd") 478 | p.Detail() 479 | p.Print("detail\n") 480 | return e.next 481 | } 482 | 483 | type adapted struct { 484 | msg string 485 | err error 486 | } 487 | 488 | func (e adapted) Error() string { return e.msg } 489 | 490 | func (e adapted) Format(s fmt.State, verb rune) { 491 | xerrors.FormatError(e, s, verb) 492 | } 493 | 494 | func (e adapted) FormatError(p xerrors.Printer) error { 495 | p.Print(e.msg) 496 | p.Detail() 497 | p.Print("detail") 498 | return e.err 499 | } 500 | 501 | // formatError is an error implementing Format instead of xerrors.Formatter. 502 | // The implementation mimics the implementation of github.com/pkg/errors. 503 | type formatError string 504 | 505 | func (e formatError) Error() string { return string(e) } 506 | 507 | func (e formatError) Format(s fmt.State, verb rune) { 508 | // Body based on pkg/errors/errors.go 509 | switch verb { 510 | case 'v': 511 | if s.Flag('+') { 512 | io.WriteString(s, string(e)) 513 | fmt.Fprintf(s, ":\n%s", "otherfile.go:456") 514 | return 515 | } 516 | fallthrough 517 | case 's': 518 | io.WriteString(s, string(e)) 519 | case 'q': 520 | fmt.Fprintf(s, "%q", string(e)) 521 | } 522 | } 523 | 524 | func (e formatError) GoString() string { 525 | panic("should never be called") 526 | } 527 | 528 | type fmtTwiceErr struct { 529 | format string 530 | args []any 531 | } 532 | 533 | func fmtTwice(format string, a ...any) error { 534 | return fmtTwiceErr{format, a} 535 | } 536 | 537 | func (e fmtTwiceErr) Error() string { return fmt.Sprint(e) } 538 | 539 | func (e fmtTwiceErr) Format(s fmt.State, verb rune) { 540 | xerrors.FormatError(e, s, verb) 541 | } 542 | 543 | func (e fmtTwiceErr) FormatError(p xerrors.Printer) (next error) { 544 | p.Printf(e.format, e.args...) 545 | p.Print("/") 546 | p.Printf(e.format, e.args...) 547 | return nil 548 | } 549 | 550 | func (e fmtTwiceErr) GoString() string { 551 | return "2 times " + fmt.Sprintf(e.format, e.args...) 552 | } 553 | 554 | type panicValue struct{} 555 | 556 | func (panicValue) String() string { panic("panic") } 557 | 558 | var rePath = regexp.MustCompile(`( [^ ]+)\/(xerrors_test|fmt_test)\.`) 559 | var reLine = regexp.MustCompile(":[0-9]+\n?$") 560 | 561 | func cleanPath(s string) string { 562 | s = rePath.ReplaceAllString(s, "/path.") 563 | s = reLine.ReplaceAllString(s, ":xxx") 564 | s = strings.ReplaceAll(s, "\n ", "") 565 | s = strings.ReplaceAll(s, " /", "/") 566 | return s 567 | } 568 | 569 | func errToParts(err error) (a []string) { 570 | for err != nil { 571 | var p testPrinter 572 | if xerrors.Unwrap(err) != nil { 573 | p.str += "wraps:" 574 | } 575 | f, ok := err.(xerrors.Formatter) 576 | if !ok { 577 | a = append(a, err.Error()) 578 | break 579 | } 580 | err = f.FormatError(&p) 581 | a = append(a, cleanPath(p.str)) 582 | } 583 | return a 584 | 585 | } 586 | 587 | type testPrinter struct { 588 | str string 589 | } 590 | 591 | func (p *testPrinter) Print(a ...any) { 592 | p.str += fmt.Sprint(a...) 593 | } 594 | 595 | func (p *testPrinter) Printf(format string, a ...any) { 596 | p.str += fmt.Sprintf(format, a...) 597 | } 598 | 599 | func (p *testPrinter) Detail() bool { 600 | p.str += " /" 601 | return true 602 | } 603 | -------------------------------------------------------------------------------- /fmt_unexported_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors 6 | 7 | import "testing" 8 | 9 | func TestParsePrintfVerb(t *testing.T) { 10 | for _, test := range []struct { 11 | in string 12 | wantSize int 13 | wantW bool 14 | }{ 15 | {"", 0, false}, 16 | {"%", 1, false}, 17 | {"%3.1", 4, false}, 18 | {"%w", 2, true}, 19 | {"%v", 2, false}, 20 | {"%3.*[4]d", 8, false}, 21 | } { 22 | gotSize, gotW := parsePrintfVerb(test.in) 23 | if gotSize != test.wantSize || gotW != test.wantW { 24 | t.Errorf("parsePrintfVerb(%q) = (%d, %t), want (%d, %t)", 25 | test.in, gotSize, gotW, test.wantSize, test.wantW) 26 | } 27 | } 28 | } 29 | 30 | func TestParsePercentW(t *testing.T) { 31 | for _, test := range []struct { 32 | in string 33 | wantIdx int 34 | wantFormat string 35 | wantOK bool 36 | }{ 37 | {"", -1, "", true}, 38 | {"%", -1, "%", true}, 39 | {"%w", 0, "%v", true}, 40 | {"%w%w", 0, "%v%v", false}, 41 | {"%3.2s %+q %% %w %#v", 2, "%3.2s %+q %% %v %#v", true}, 42 | {"%3.2s %w %% %w %#v", 1, "%3.2s %v %% %v %#v", false}, 43 | } { 44 | gotIdx, gotFormat, gotOK := parsePercentW(test.in) 45 | if gotIdx != test.wantIdx || gotFormat != test.wantFormat || gotOK != test.wantOK { 46 | t.Errorf("parsePercentW(%q) = (%d, %q, %t), want (%d, %q, %t)", 47 | test.in, gotIdx, gotFormat, gotOK, test.wantIdx, test.wantFormat, test.wantOK) 48 | 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors 6 | 7 | // A Formatter formats error messages. 8 | type Formatter interface { 9 | error 10 | 11 | // FormatError prints the receiver's first error and returns the next error in 12 | // the error chain, if any. 13 | FormatError(p Printer) (next error) 14 | } 15 | 16 | // A Printer formats error messages. 17 | // 18 | // The most common implementation of Printer is the one provided by package fmt 19 | // during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message 20 | // typically provide their own implementations. 21 | type Printer interface { 22 | // Print appends args to the message output. 23 | Print(args ...any) 24 | 25 | // Printf writes a formatted string. 26 | Printf(format string, args ...any) 27 | 28 | // Detail reports whether error detail is requested. 29 | // After the first call to Detail, all text written to the Printer 30 | // is formatted as additional detail, or ignored when 31 | // detail has not been requested. 32 | // If Detail returns false, the caller can avoid printing the detail at all. 33 | Detail() bool 34 | } 35 | -------------------------------------------------------------------------------- /frame.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors 6 | 7 | import ( 8 | "runtime" 9 | ) 10 | 11 | // A Frame contains part of a call stack. 12 | type Frame struct { 13 | // Make room for three PCs: the one we were asked for, what it called, 14 | // and possibly a PC for skipPleaseUseCallersFrames. See: 15 | // https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169 16 | frames [3]uintptr 17 | } 18 | 19 | // Caller returns a Frame that describes a frame on the caller's stack. 20 | // The argument skip is the number of frames to skip over. 21 | // Caller(0) returns the frame for the caller of Caller. 22 | func Caller(skip int) Frame { 23 | var s Frame 24 | runtime.Callers(skip+1, s.frames[:]) 25 | return s 26 | } 27 | 28 | // location reports the file, line, and function of a frame. 29 | // 30 | // The returned function may be "" even if file and line are not. 31 | func (f Frame) location() (function, file string, line int) { 32 | frames := runtime.CallersFrames(f.frames[:]) 33 | if _, ok := frames.Next(); !ok { 34 | return "", "", 0 35 | } 36 | fr, ok := frames.Next() 37 | if !ok { 38 | return "", "", 0 39 | } 40 | return fr.Function, fr.File, fr.Line 41 | } 42 | 43 | // Format prints the stack as error detail. 44 | // It should be called from an error's Format implementation 45 | // after printing any other error detail. 46 | func (f Frame) Format(p Printer) { 47 | if p.Detail() { 48 | function, file, line := f.location() 49 | if function != "" { 50 | p.Printf("%s\n ", function) 51 | } 52 | if file != "" { 53 | p.Printf("%s:%d\n", file, line) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module golang.org/x/xerrors 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /internal/internal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package internal 6 | 7 | // EnableTrace indicates whether stack information should be recorded in errors. 8 | var EnableTrace = true 9 | -------------------------------------------------------------------------------- /stack_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors_test 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "math/big" 11 | "testing" 12 | 13 | "golang.org/x/xerrors" 14 | "golang.org/x/xerrors/internal" 15 | ) 16 | 17 | type myType struct{} 18 | 19 | func (myType) Format(s fmt.State, v rune) { 20 | s.Write(bytes.Repeat([]byte("Hi! "), 10)) 21 | } 22 | 23 | func BenchmarkErrorf(b *testing.B) { 24 | err := xerrors.New("foo") 25 | // pi := big.NewFloat(3.14) // Something expensive. 26 | num := big.NewInt(5) 27 | args := func(a ...any) []any { return a } 28 | benchCases := []struct { 29 | name string 30 | format string 31 | args []any 32 | }{ 33 | {"no_format", "msg: %v", args(err)}, 34 | {"with_format", "failed %d times: %v", args(5, err)}, 35 | {"method: mytype", "pi: %v", args("myfile.go", myType{}, err)}, 36 | {"method: number", "pi: %v", args("myfile.go", num, err)}, 37 | } 38 | for _, bc := range benchCases { 39 | b.Run(bc.name, func(b *testing.B) { 40 | b.Run("ExpWithTrace", func(b *testing.B) { 41 | for i := 0; i < b.N; i++ { 42 | xerrors.Errorf(bc.format, bc.args...) 43 | } 44 | }) 45 | b.Run("ExpNoTrace", func(b *testing.B) { 46 | internal.EnableTrace = false 47 | defer func() { internal.EnableTrace = true }() 48 | 49 | for i := 0; i < b.N; i++ { 50 | xerrors.Errorf(bc.format, bc.args...) 51 | } 52 | }) 53 | b.Run("Core", func(b *testing.B) { 54 | for i := 0; i < b.N; i++ { 55 | fmt.Errorf(bc.format, bc.args...) 56 | } 57 | }) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /wrap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors 6 | 7 | import "errors" 8 | 9 | // A Wrapper provides context around another error. 10 | type Wrapper interface { 11 | // Unwrap returns the next error in the error chain. 12 | // If there is no next error, Unwrap returns nil. 13 | Unwrap() error 14 | } 15 | 16 | // Opaque returns an error with the same error formatting as err 17 | // but that does not match err and cannot be unwrapped. 18 | func Opaque(err error) error { 19 | return noWrapper{err} 20 | } 21 | 22 | type noWrapper struct { 23 | error 24 | } 25 | 26 | func (e noWrapper) FormatError(p Printer) (next error) { 27 | if f, ok := e.error.(Formatter); ok { 28 | return f.FormatError(p) 29 | } 30 | p.Print(e.error) 31 | return nil 32 | } 33 | 34 | // Unwrap returns the result of calling the Unwrap method on err, if err implements 35 | // Unwrap. Otherwise, Unwrap returns nil. 36 | // 37 | // Unwrap only calls a method of the form "Unwrap() error". 38 | // In particular Unwrap does not unwrap errors returned by [errors.Join]. 39 | // 40 | // Deprecated: As of Go 1.13, this function simply calls [errors.Unwrap]. 41 | func Unwrap(err error) error { return errors.Unwrap(err) } 42 | 43 | // Is reports whether any error in err's tree matches target. 44 | // 45 | // The tree consists of err itself, followed by the errors obtained by repeatedly 46 | // calling its Unwrap() error or Unwrap() []error method. When err wraps multiple 47 | // errors, Is examines err followed by a depth-first traversal of its children. 48 | // 49 | // An error is considered to match a target if it is equal to that target or if 50 | // it implements a method Is(error) bool such that Is(target) returns true. 51 | // 52 | // An error type might provide an Is method so it can be treated as equivalent 53 | // to an existing error. For example, if MyError defines 54 | // 55 | // func (m MyError) Is(target error) bool { return target == fs.ErrExist } 56 | // 57 | // then Is(MyError{}, fs.ErrExist) returns true. See [syscall.Errno.Is] for 58 | // an example in the standard library. An Is method should only shallowly 59 | // compare err and the target and not call [Unwrap] on either. 60 | // 61 | // Deprecated: As of Go 1.13, this function simply calls [errors.Is]. 62 | func Is(err, target error) bool { return errors.Is(err, target) } 63 | 64 | // As finds the first error in err's tree that matches target, and if one is found, 65 | // sets target to that error value and returns true. Otherwise, it returns false. 66 | // 67 | // The tree consists of err itself, followed by the errors obtained by repeatedly 68 | // calling its Unwrap() error or Unwrap() []error method. When err wraps multiple 69 | // errors, As examines err followed by a depth-first traversal of its children. 70 | // 71 | // An error matches target if the error's concrete value is assignable to the value 72 | // pointed to by target, or if the error has a method As(any) bool such that 73 | // As(target) returns true. In the latter case, the As method is responsible for 74 | // setting target. 75 | // 76 | // An error type might provide an As method so it can be treated as if it were a 77 | // different error type. 78 | // 79 | // As panics if target is not a non-nil pointer to either a type that implements 80 | // error, or to any interface type. 81 | // 82 | // Deprecated: As of Go 1.13, this function simply calls [errors.As]. 83 | func As(err error, target any) bool { return errors.As(err, target) } 84 | -------------------------------------------------------------------------------- /wrap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package xerrors_test 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "os" 11 | "testing" 12 | 13 | "golang.org/x/xerrors" 14 | ) 15 | 16 | func TestIs(t *testing.T) { 17 | err1 := xerrors.New("1") 18 | erra := xerrors.Errorf("wrap 2: %w", err1) 19 | errb := xerrors.Errorf("wrap 3: %w", erra) 20 | erro := xerrors.Opaque(err1) 21 | errco := xerrors.Errorf("opaque: %w", erro) 22 | err3 := xerrors.New("3") 23 | 24 | poser := &poser{"either 1 or 3", func(err error) bool { 25 | return err == err1 || err == err3 26 | }} 27 | 28 | testCases := []struct { 29 | err error 30 | target error 31 | match bool 32 | }{ 33 | {nil, nil, true}, 34 | {nil, err1, false}, 35 | {err1, nil, false}, 36 | {err1, err1, true}, 37 | {erra, err1, true}, 38 | {errb, err1, true}, 39 | {errco, erro, true}, 40 | {errco, err1, false}, 41 | {erro, erro, true}, 42 | {err1, err3, false}, 43 | {erra, err3, false}, 44 | {errb, err3, false}, 45 | {poser, err1, true}, 46 | {poser, err3, true}, 47 | {poser, erra, false}, 48 | {poser, errb, false}, 49 | {poser, erro, false}, 50 | {poser, errco, false}, 51 | {errorUncomparable{}, errorUncomparable{}, true}, 52 | {errorUncomparable{}, &errorUncomparable{}, false}, 53 | {&errorUncomparable{}, errorUncomparable{}, true}, 54 | {&errorUncomparable{}, &errorUncomparable{}, false}, 55 | {errorUncomparable{}, err1, false}, 56 | {&errorUncomparable{}, err1, false}, 57 | } 58 | for _, tc := range testCases { 59 | t.Run("", func(t *testing.T) { 60 | if got := xerrors.Is(tc.err, tc.target); got != tc.match { 61 | t.Errorf("Is(%v, %v) = %v, want %v", tc.err, tc.target, got, tc.match) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | type poser struct { 68 | msg string 69 | f func(error) bool 70 | } 71 | 72 | func (p *poser) Error() string { return p.msg } 73 | func (p *poser) Is(err error) bool { return p.f(err) } 74 | func (p *poser) As(err any) bool { 75 | switch x := err.(type) { 76 | case **poser: 77 | *x = p 78 | case *errorT: 79 | *x = errorT{} 80 | case **os.PathError: 81 | *x = &os.PathError{} 82 | default: 83 | return false 84 | } 85 | return true 86 | } 87 | 88 | func TestErrorsIs(t *testing.T) { 89 | var errSentinel = errors.New("sentinel") 90 | 91 | got := errors.Is(xerrors.Errorf("%w", errSentinel), errSentinel) 92 | if !got { 93 | t.Error("got false, want true") 94 | } 95 | 96 | got = errors.Is(xerrors.Errorf("%w: %s", errSentinel, "foo"), errSentinel) 97 | if !got { 98 | t.Error("got false, want true") 99 | } 100 | } 101 | 102 | func TestAs(t *testing.T) { 103 | var errT errorT 104 | var errP *os.PathError 105 | var timeout interface{ Timeout() bool } 106 | var p *poser 107 | _, errF := os.Open("non-existing") 108 | 109 | testCases := []struct { 110 | err error 111 | target any 112 | match bool 113 | }{{ 114 | nil, 115 | &errP, 116 | false, 117 | }, { 118 | xerrors.Errorf("pittied the fool: %w", errorT{}), 119 | &errT, 120 | true, 121 | }, { 122 | errF, 123 | &errP, 124 | true, 125 | }, { 126 | xerrors.Opaque(errT), 127 | &errT, 128 | false, 129 | }, { 130 | errorT{}, 131 | &errP, 132 | false, 133 | }, { 134 | errWrap{nil}, 135 | &errT, 136 | false, 137 | }, { 138 | &poser{"error", nil}, 139 | &errT, 140 | true, 141 | }, { 142 | &poser{"path", nil}, 143 | &errP, 144 | true, 145 | }, { 146 | &poser{"oh no", nil}, 147 | &p, 148 | true, 149 | }, { 150 | xerrors.New("err"), 151 | &timeout, 152 | false, 153 | }, { 154 | errF, 155 | &timeout, 156 | true, 157 | }, { 158 | xerrors.Errorf("path error: %w", errF), 159 | &timeout, 160 | true, 161 | }} 162 | for i, tc := range testCases { 163 | name := fmt.Sprintf("%d:As(Errorf(..., %v), %v)", i, tc.err, tc.target) 164 | t.Run(name, func(t *testing.T) { 165 | match := xerrors.As(tc.err, tc.target) 166 | if match != tc.match { 167 | t.Fatalf("xerrors.As(%T, %T): got %v; want %v", tc.err, tc.target, match, tc.match) 168 | } 169 | if !match { 170 | return 171 | } 172 | if tc.target == nil { 173 | t.Fatalf("non-nil result after match") 174 | } 175 | }) 176 | } 177 | } 178 | 179 | func TestAsValidation(t *testing.T) { 180 | var s string 181 | testCases := []any{ 182 | nil, 183 | (*int)(nil), 184 | "error", 185 | &s, 186 | } 187 | err := xerrors.New("error") 188 | for _, tc := range testCases { 189 | t.Run(fmt.Sprintf("%T(%v)", tc, tc), func(t *testing.T) { 190 | defer func() { 191 | recover() 192 | }() 193 | if xerrors.As(err, tc) { 194 | t.Errorf("As(err, %T(%v)) = true, want false", tc, tc) 195 | return 196 | } 197 | t.Errorf("As(err, %T(%v)) did not panic", tc, tc) 198 | }) 199 | } 200 | } 201 | 202 | func TestUnwrap(t *testing.T) { 203 | err1 := xerrors.New("1") 204 | erra := xerrors.Errorf("wrap 2: %w", err1) 205 | erro := xerrors.Opaque(err1) 206 | 207 | testCases := []struct { 208 | err error 209 | want error 210 | }{ 211 | {nil, nil}, 212 | {errWrap{nil}, nil}, 213 | {err1, nil}, 214 | {erra, err1}, 215 | {xerrors.Errorf("wrap 3: %w", erra), erra}, 216 | 217 | {erro, nil}, 218 | {xerrors.Errorf("opaque: %w", erro), erro}, 219 | } 220 | for _, tc := range testCases { 221 | if got := xerrors.Unwrap(tc.err); got != tc.want { 222 | t.Errorf("Unwrap(%v) = %v, want %v", tc.err, got, tc.want) 223 | } 224 | } 225 | } 226 | 227 | func TestOpaque(t *testing.T) { 228 | got := fmt.Sprintf("%v", xerrors.Errorf("foo: %v", xerrors.Opaque(errorT{}))) 229 | want := "foo: errorT" 230 | if got != want { 231 | t.Errorf("error without Format: got %v; want %v", got, want) 232 | } 233 | 234 | got = fmt.Sprintf("%v", xerrors.Errorf("foo: %v", xerrors.Opaque(errorD{}))) 235 | want = "foo: errorD" 236 | if got != want { 237 | t.Errorf("error with Format: got %v; want %v", got, want) 238 | } 239 | } 240 | 241 | type errorT struct{} 242 | 243 | func (errorT) Error() string { return "errorT" } 244 | 245 | type errorD struct{} 246 | 247 | func (errorD) Error() string { return "errorD" } 248 | 249 | func (errorD) FormatError(p xerrors.Printer) error { 250 | p.Print("errorD") 251 | p.Detail() 252 | p.Print("detail") 253 | return nil 254 | } 255 | 256 | type errWrap struct{ error } 257 | 258 | func (errWrap) Error() string { return "wrapped" } 259 | 260 | func (errWrap) Unwrap() error { return nil } 261 | 262 | type errorUncomparable struct { 263 | f []string 264 | } 265 | 266 | func (errorUncomparable) Error() string { 267 | return "uncomparable error" 268 | } 269 | 270 | func (errorUncomparable) Is(target error) bool { 271 | _, ok := target.(errorUncomparable) 272 | return ok 273 | } 274 | --------------------------------------------------------------------------------