diff --git a/adaptor.go b/adaptor.go new file mode 100644 index 0000000..899dd3c --- /dev/null +++ b/adaptor.go @@ -0,0 +1,189 @@ +package errorx + +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("") + } + 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 +} diff --git a/errorx.go b/errorx.go index 621c83d..287cb16 100644 --- a/errorx.go +++ b/errorx.go @@ -59,3 +59,13 @@ type errorString struct { func (e *errorString) Error() string { return e.s } + +func (e *errorString) Format(s fmt.State, v rune) { + FormatError(e, s, v) +} + +func (e *errorString) FormatError(p Printer) (next error) { + p.Print(e.s) + e.frame.Format(p) + return nil +} diff --git a/format.go b/format.go new file mode 100644 index 0000000..48b351f --- /dev/null +++ b/format.go @@ -0,0 +1,12 @@ +package errorx + +type Formatter interface { + error + FormatError(p Printer) (next error) +} + +type Printer interface { + Print(args ...any) + Printf(format string, args ...any) + Detail() bool +} diff --git a/frame.go b/frame.go index 0a08f23..98a6525 100644 --- a/frame.go +++ b/frame.go @@ -21,6 +21,18 @@ func (f Frame) Location() (function, file string, line int) { return fr.Function, fr.File, fr.Line } +func (f Frame) Format(p Printer) { + if p.Detail() { + function, file, line := f.Location() + if function != "" { + p.Printf("%s\n ", function) + } + if file != "" { + p.Printf("%s:%d\n", file, line) + } + } +} + func caller(skip int) Frame { var s Frame runtime.Callers(skip+1, s.frames[:]) diff --git a/wrap.go b/wrap.go new file mode 100644 index 0000000..ed8e09d --- /dev/null +++ b/wrap.go @@ -0,0 +1,63 @@ +package errorx + +import "fmt" + +func Trace(err error) error { + if err == nil { + return nil + } + + frame := Frame{} + if IsTracingEnabled() { + frame = caller(1) + } + return &wrapError{ + err: err, + frame: frame, + } +} + +func Wrapf(err error, format string, a ...any) error { + if err == nil { + return nil + } + + frame := Frame{} + if IsTracingEnabled() { + frame = caller(1) + } + + msg := format + if a != nil { + msg = fmt.Sprintf(format, a...) + } + return &wrapError{ + err: err, + msg: msg, + frame: frame, + } +} + +type wrapError struct { + err error + msg string + 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 +}