| // 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 ( |
| "bytes" |
| "fmt" |
| "io" |
| "reflect" |
| "strconv" |
| ) |
| |
| // FormatError calls the FormatError method of f with an errors.Printer |
| // configured according to s and verb, and writes the result to s. |
| func FormatError(f Formatter, s fmt.State, verb rune) { |
| // Assuming this function is only called from the Format method, and given |
| // that FormatError takes precedence over Format, it cannot be called from |
| // any package that supports errors.Formatter. It is therefore safe to |
| // disregard that State may be a specific printer implementation and use one |
| // of our choice instead. |
| |
| // limitations: does not support printing error as Go struct. |
| |
| var ( |
| sep = " " // separator before next error |
| p = &state{State: s} |
| direct = true |
| ) |
| |
| var err error = f |
| |
| switch verb { |
| // Note that this switch must match the preference order |
| // for ordinary string printing (%#v before %+v, and so on). |
| |
| case 'v': |
| if s.Flag('#') { |
| if stringer, ok := err.(fmt.GoStringer); ok { |
| io.WriteString(&p.buf, stringer.GoString()) |
| goto exit |
| } |
| // proceed as if it were %v |
| } else if s.Flag('+') { |
| p.printDetail = true |
| sep = "\n - " |
| } |
| case 's': |
| case 'q', 'x', 'X': |
| // Use an intermediate buffer in the rare cases that precision, |
| // truncation, or one of the alternative verbs (q, x, and X) are |
| // specified. |
| direct = false |
| |
| default: |
| p.buf.WriteString("%!") |
| p.buf.WriteRune(verb) |
| p.buf.WriteByte('(') |
| switch { |
| case err != nil: |
| p.buf.WriteString(reflect.TypeOf(f).String()) |
| default: |
| p.buf.WriteString("<nil>") |
| } |
| p.buf.WriteByte(')') |
| io.Copy(s, &p.buf) |
| return |
| } |
| |
| loop: |
| for { |
| switch v := err.(type) { |
| case Formatter: |
| err = v.FormatError((*printer)(p)) |
| case fmt.Formatter: |
| v.Format(p, 'v') |
| break loop |
| default: |
| io.WriteString(&p.buf, v.Error()) |
| break loop |
| } |
| if err == nil { |
| break |
| } |
| if p.needColon || !p.printDetail { |
| p.buf.WriteByte(':') |
| p.needColon = false |
| } |
| p.buf.WriteString(sep) |
| p.inDetail = false |
| p.needNewline = false |
| } |
| |
| exit: |
| width, okW := s.Width() |
| prec, okP := s.Precision() |
| |
| if !direct || (okW && width > 0) || okP { |
| // Construct format string from State s. |
| format := []byte{'%'} |
| if s.Flag('-') { |
| format = append(format, '-') |
| } |
| if s.Flag('+') { |
| format = append(format, '+') |
| } |
| if s.Flag(' ') { |
| format = append(format, ' ') |
| } |
| if okW { |
| format = strconv.AppendInt(format, int64(width), 10) |
| } |
| if okP { |
| format = append(format, '.') |
| format = strconv.AppendInt(format, int64(prec), 10) |
| } |
| format = append(format, string(verb)...) |
| fmt.Fprintf(s, string(format), p.buf.String()) |
| } else { |
| io.Copy(s, &p.buf) |
| } |
| } |
| |
| var detailSep = []byte("\n ") |
| |
| // state tracks error printing state. It implements fmt.State. |
| type state struct { |
| fmt.State |
| buf bytes.Buffer |
| |
| printDetail bool |
| inDetail bool |
| needColon bool |
| needNewline bool |
| } |
| |
| func (s *state) Write(b []byte) (n int, err error) { |
| if s.printDetail { |
| if len(b) == 0 { |
| return 0, nil |
| } |
| if s.inDetail && s.needColon { |
| s.needNewline = true |
| if b[0] == '\n' { |
| b = b[1:] |
| } |
| } |
| k := 0 |
| for i, c := range b { |
| if s.needNewline { |
| if s.inDetail && s.needColon { |
| s.buf.WriteByte(':') |
| s.needColon = false |
| } |
| s.buf.Write(detailSep) |
| s.needNewline = false |
| } |
| if c == '\n' { |
| s.buf.Write(b[k:i]) |
| k = i + 1 |
| s.needNewline = true |
| } |
| } |
| s.buf.Write(b[k:]) |
| if !s.inDetail { |
| s.needColon = true |
| } |
| } else if !s.inDetail { |
| s.buf.Write(b) |
| } |
| return len(b), nil |
| } |
| |
| // printer wraps a state to implement an xerrors.Printer. |
| type printer state |
| |
| func (s *printer) Print(args ...interface{}) { |
| if !s.inDetail || s.printDetail { |
| fmt.Fprint((*state)(s), args...) |
| } |
| } |
| |
| func (s *printer) Printf(format string, args ...interface{}) { |
| if !s.inDetail || s.printDetail { |
| fmt.Fprintf((*state)(s), format, args...) |
| } |
| } |
| |
| func (s *printer) Detail() bool { |
| s.inDetail = true |
| return s.printDetail |
| } |