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