// 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 }