Skip to content

Commit

Permalink
Add Wrapf and formatting declarations
Browse files Browse the repository at this point in the history
  • Loading branch information
cristaloleg committed Jun 2, 2023
1 parent 0bfdcf6 commit 8c3642f
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 0 deletions.
189 changes: 189 additions & 0 deletions adaptor.go
Original file line number Diff line number Diff line change
@@ -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("<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
}
10 changes: 10 additions & 0 deletions errorx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
12 changes: 12 additions & 0 deletions format.go
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -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[:])
Expand Down
63 changes: 63 additions & 0 deletions wrap.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 8c3642f

Please sign in to comment.