cockroachdb / errors

Go error library with error portability over the network
Apache License 2.0
2.07k stars 66 forks source link

infinite recursion for seemingly idiomatic impl of Error() string for redactable error #88

Closed tbg closed 2 years ago

tbg commented 2 years ago

The following produces infinite recursion when Error is invoked:

var _ errors.SafeFormatter = (*MyFooError)(nil)
var _ fmt.Formatter = (*MyFooError)(nil)

// SafeFormatError implements errors.SafeFormatter.
func (e *MyFooError) SafeFormatError(p errors.Printer) error {
    p.Printf("I am the error text and would like to not be duplicated")
    return nil
}

// Format implements fmt.Formatter.
func (e *MyFooError) Format(s fmt.State, verb rune) { errors.FormatError(e, s, verb) }

// Error implements error.
func (e *MyFooError) Error() string {
    return  fmt.Sprint(e)
}

func (e *MyFooError) String() string {
    return redact.Sprint(e).StripMarkers()
}

However, this seems idiomatic.

The problem appears to be that FormatError will at one point load the error mark and invoke Error(), which re-enters the machinery from the top.

There is a workaround:

func (e *MyFooError) printf(printf func(string, ...interface{})) {
  printf("I am the error text")
}

func (e *MyFooError) Error() string {
  var s string
  e.printf(func(format string, strings ...interface{}) { s = fmt.Sprintf(format, strings...) }
 return s
}

func (e *MyFooError) SafeFormatError(p errors.Printer) error {
  e.printf(p.Printf)
  return nil
}

but I found the behavior surprising.