| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package xerrors |
| |
| import ( |
| "fmt" |
| "strings" |
| "unicode" |
| "unicode/utf8" |
| |
| "golang.org/x/xerrors/internal" |
| ) |
| |
| const percentBangString = "%!" |
| |
| // Errorf formats according to a format specifier and returns the string as a |
| // value that satisfies error. |
| // |
| // The returned error includes the file and line number of the caller when |
| // formatted with additional detail enabled. If the last argument is an error |
| // the returned error's Format method will return it if the format string ends |
| // with ": %s", ": %v", or ": %w". If the last argument is an error and the |
| // format string ends with ": %w", the returned error implements an Unwrap |
| // method returning it. |
| // |
| // If the format specifier includes a %w verb with an error operand in a |
| // position other than at the end, the returned error will still implement an |
| // Unwrap method returning the operand, but the error's Format method will not |
| // return the wrapped error. |
| // |
| // It is invalid to include more than one %w verb or to supply it with an |
| // operand that does not implement the error interface. The %w verb is otherwise |
| // a synonym for %v. |
| // |
| // Note that as of Go 1.13, the fmt.Errorf function will do error formatting, |
| // but it will not capture a stack backtrace. |
| func Errorf(format string, a ...any) error { |
| format = formatPlusW(format) |
| // Support a ": %[wsv]" suffix, which works well with xerrors.Formatter. |
| wrap := strings.HasSuffix(format, ": %w") |
| idx, format2, ok := parsePercentW(format) |
| percentWElsewhere := !wrap && idx >= 0 |
| if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) { |
| err := errorAt(a, len(a)-1) |
| if err == nil { |
| return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)} |
| } |
| // TODO: this is not entirely correct. The error value could be |
| // printed elsewhere in format if it mixes numbered with unnumbered |
| // substitutions. With relatively small changes to doPrintf we can |
| // have it optionally ignore extra arguments and pass the argument |
| // list in its entirety. |
| msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...) |
| frame := Frame{} |
| if internal.EnableTrace { |
| frame = Caller(1) |
| } |
| if wrap { |
| return &wrapError{msg, err, frame} |
| } |
| return &noWrapError{msg, err, frame} |
| } |
| // Support %w anywhere. |
| // TODO: don't repeat the wrapped error's message when %w occurs in the middle. |
| msg := fmt.Sprintf(format2, a...) |
| if idx < 0 { |
| return &noWrapError{msg, nil, Caller(1)} |
| } |
| err := errorAt(a, idx) |
| if !ok || err == nil { |
| // Too many %ws or argument of %w is not an error. Approximate the Go |
| // 1.13 fmt.Errorf message. |
| return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)} |
| } |
| frame := Frame{} |
| if internal.EnableTrace { |
| frame = Caller(1) |
| } |
| return &wrapError{msg, err, frame} |
| } |
| |
| func errorAt(args []any, i int) error { |
| if i < 0 || i >= len(args) { |
| return nil |
| } |
| err, ok := args[i].(error) |
| if !ok { |
| return nil |
| } |
| return err |
| } |
| |
| // formatPlusW is used to avoid the vet check that will barf at %w. |
| func formatPlusW(s string) string { |
| return s |
| } |
| |
| // Return the index of the only %w in format, or -1 if none. |
| // Also return a rewritten format string with %w replaced by %v, and |
| // false if there is more than one %w. |
| // TODO: handle "%[N]w". |
| func parsePercentW(format string) (idx int, newFormat string, ok bool) { |
| // Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go. |
| idx = -1 |
| ok = true |
| n := 0 |
| sz := 0 |
| var isW bool |
| for i := 0; i < len(format); i += sz { |
| if format[i] != '%' { |
| sz = 1 |
| continue |
| } |
| // "%%" is not a format directive. |
| if i+1 < len(format) && format[i+1] == '%' { |
| sz = 2 |
| continue |
| } |
| sz, isW = parsePrintfVerb(format[i:]) |
| if isW { |
| if idx >= 0 { |
| ok = false |
| } else { |
| idx = n |
| } |
| // "Replace" the last character, the 'w', with a 'v'. |
| p := i + sz - 1 |
| format = format[:p] + "v" + format[p+1:] |
| } |
| n++ |
| } |
| return idx, format, ok |
| } |
| |
| // Parse the printf verb starting with a % at s[0]. |
| // Return how many bytes it occupies and whether the verb is 'w'. |
| func parsePrintfVerb(s string) (int, bool) { |
| // Assume only that the directive is a sequence of non-letters followed by a single letter. |
| sz := 0 |
| var r rune |
| for i := 1; i < len(s); i += sz { |
| r, sz = utf8.DecodeRuneInString(s[i:]) |
| if unicode.IsLetter(r) { |
| return i + sz, r == 'w' |
| } |
| } |
| return len(s), false |
| } |
| |
| type noWrapError struct { |
| msg string |
| err error |
| frame Frame |
| } |
| |
| func (e *noWrapError) Error() string { |
| return fmt.Sprint(e) |
| } |
| |
| func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } |
| |
| func (e *noWrapError) FormatError(p Printer) (next error) { |
| p.Print(e.msg) |
| e.frame.Format(p) |
| return e.err |
| } |
| |
| type wrapError struct { |
| msg string |
| err error |
| frame Frame |
| } |
| |
| func (e *wrapError) Error() string { |
| return fmt.Sprint(e) |
| } |
| |
| func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } |
| |
| func (e *wrapError) FormatError(p Printer) (next error) { |
| p.Print(e.msg) |
| e.frame.Format(p) |
| return e.err |
| } |
| |
| func (e *wrapError) Unwrap() error { |
| return e.err |
| } |