blob: f17ddb1072b0026255467e04a9ea4e4891ea740b [file] [log] [blame]
// 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 fmt
import (
"bytes"
"strings"
"golang.org/x/exp/errors"
"golang.org/x/exp/errors/internal"
)
// 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 errors.Wrapper
// with an Unwrap method returning it.
func Errorf(format string, a ...interface{}) error {
err, wrap := lastError(format, a)
if err == nil {
return &noWrapError{Sprintf(format, a...), nil, errors.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 := Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
frame := errors.Frame{}
if internal.EnableTrace {
frame = errors.Caller(1)
}
if wrap {
return &wrapError{msg, err, frame}
}
return &noWrapError{msg, err, frame}
}
func lastError(format string, a []interface{}) (err error, wrap bool) {
wrap = strings.HasSuffix(format, ": %w")
if !wrap &&
!strings.HasSuffix(format, ": %s") &&
!strings.HasSuffix(format, ": %v") {
return nil, false
}
if len(a) == 0 {
return nil, false
}
err, ok := a[len(a)-1].(error)
if !ok {
return nil, false
}
return err, wrap
}
type noWrapError struct {
msg string
err error
frame errors.Frame
}
func (e *noWrapError) Error() string {
return Sprint(e)
}
func (e *noWrapError) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
type wrapError struct {
msg string
err error
frame errors.Frame
}
func (e *wrapError) Error() string {
return Sprint(e)
}
func (e *wrapError) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
func (e *wrapError) Unwrap() error {
return e.err
}
func fmtError(p *pp, verb rune, err error) (handled bool) {
var (
sep = " " // separator before next error
w = p // print buffer where error text is written
)
switch {
// Note that this switch must match the preference order
// for ordinary string printing (%#v before %+v, and so on).
case p.fmt.sharpV:
if stringer, ok := p.arg.(GoStringer); ok {
// Print the result of GoString unadorned.
p.fmt.fmtS(stringer.GoString())
return true
}
return false
case p.fmt.plusV:
sep = "\n - "
w.fmt.fmtFlags = fmtFlags{plusV: p.fmt.plusV} // only keep detail flag
// The width or precision of a detailed view could be the number of
// errors to print from a list.
default:
// Use an intermediate buffer in the rare cases that precision,
// truncation, or one of the alternative verbs (q, x, and X) are
// specified.
switch verb {
case 's', 'v':
if (!w.fmt.widPresent || w.fmt.wid == 0) && !w.fmt.precPresent {
break
}
fallthrough
case 'q', 'x', 'X':
w = newPrinter()
defer w.free()
default:
w.badVerb(verb)
return true
}
}
loop:
for {
w.fmt.inDetail = false
switch v := err.(type) {
case errors.Formatter:
err = v.FormatError((*errPP)(w))
case Formatter:
if w.fmt.plusV {
v.Format((*errPPState)(w), 'v') // indent new lines
} else {
v.Format(w, 'v') // do not indent new lines
}
break loop
default:
w.fmtString(v.Error(), 's')
break loop
}
if err == nil {
break
}
if !w.fmt.inDetail || !p.fmt.plusV {
w.buf.WriteByte(':')
}
// Strip last newline of detail.
if bytes.HasSuffix([]byte(w.buf), detailSep) {
w.buf = w.buf[:len(w.buf)-len(detailSep)]
}
w.buf.WriteString(sep)
w.fmt.inDetail = false
}
if w != p {
p.fmtString(string(w.buf), verb)
}
return true
}
var detailSep = []byte("\n ")
// errPPState wraps a pp to implement State with indentation. It is used
// for errors implementing fmt.Formatter.
type errPPState pp
func (p *errPPState) Width() (wid int, ok bool) { return (*pp)(p).Width() }
func (p *errPPState) Precision() (prec int, ok bool) { return (*pp)(p).Precision() }
func (p *errPPState) Flag(c int) bool { return (*pp)(p).Flag(c) }
func (p *errPPState) Write(b []byte) (n int, err error) {
if !p.fmt.inDetail || p.fmt.plusV {
if len(b) == 0 {
return 0, nil
}
if p.fmt.inDetail && p.fmt.needNewline {
p.fmt.needNewline = false
p.buf.WriteByte(':')
p.buf.Write(detailSep)
if b[0] == '\n' {
b = b[1:]
}
}
k := 0
for i, c := range b {
if c == '\n' {
p.buf.Write(b[k:i])
p.buf.Write(detailSep)
k = i + 1
}
}
p.buf.Write(b[k:])
p.fmt.needNewline = !p.fmt.inDetail
}
return len(b), nil
}
// errPP wraps a pp to implement an errors.Printer.
type errPP pp
func (p *errPP) Print(args ...interface{}) {
if !p.fmt.inDetail || p.fmt.plusV {
if p.fmt.plusV {
Fprint((*errPPState)(p), args...)
} else {
(*pp)(p).doPrint(args)
}
}
}
func (p *errPP) Printf(format string, args ...interface{}) {
if !p.fmt.inDetail || p.fmt.plusV {
if p.fmt.plusV {
Fprintf((*errPPState)(p), format, args...)
} else {
(*pp)(p).doPrintf(format, args)
}
}
}
func (p *errPP) Detail() bool {
p.fmt.inDetail = true
return p.fmt.plusV
}