MDobak / go-xerrors

The go-xerrors is an idiomatic and lightweight package that provides a set of functions to make working with errors easier.
MIT License
42 stars 1 forks source link

Expose wrapper error #3

Open bherw opened 1 year ago

bherw commented 1 year ago

Thanks for the great little library, it solves a lot of what we've been looking for.

It'd be useful if the withWrapper struct (and Wrapper interface) would expose the wrapper error. Right now, the format function produces something like this for nested errors:

Error: wrapper 2: wrapper 1: original error
Previous error: wrapper 1: original error
Previous error: original error

The testError in format_test.go displays roughly what I want, but only by implementing a different Error() function.

I'd also like to be able to enable/disable stacktraces while maintaining this list of causes; I'm fine with writing my own formatter, but I'd rather avoid replacing the wrappedError and xerrors.New constructor.

I'm happy to submit a PR if you're OK with this direction.

MDobak commented 1 year ago

Hey! Thank you for your suggestions.

Regarding the first point you raised:

It'd be useful if the withWrapper struct (and Wrapper interface) would expose the wrapper error. Right now, the format function produces something like this for nested errors:

I'm not sure if I understand you correctly. The "Previous error:" part of the error only appears only if a nested error implements the DetailedError interface, which returns additional information about errors, such as a stack trace.

For example:

e := xerrors.Message("original error")
e = xerrors.WithWrapper(xerrors.Message("wrapper 1"), e)
e = xerrors.WithWrapper(xerrors.Message("wrapper 2"), e)
xerrors.Print(e)

Will only print one line.

Error: wrapper 2: wrapper 1: original error

This is because none of the nested errors implements the DetailedError interface. In that case, then the default error formatter will not repeat the error.

Also, the reason why the package prints all of the errors after the colon is that there is no way to extract a message that wraps an error. Therefore, it only gets what Error() returns and prints it, and the Error() method will always return all errors separated by a colon.

One possible solution could be to create an interface that provides a method like ShortError() that only returns a message before the wrapped error. However, this approach has a few issues. Firstly, it would only work with errors that use that interface. If you use fmt.Errorf, an error returned by this function will print all errors separated by a colon anyway. Secondly, as I mentioned earlier, the formatter only prints errors that implement the DetailedError interface. Therefore, if you have a chain of wrapped errors where none of them implements the DetailedError interface and the first one would have the ShortError method, then only the first error will print and information about all wrapped errors will be lost.

I'd also like to be able to enable/disable stacktraces while maintaining this list of causes; I'm fine with writing my own formatter, but I'd rather avoid replacing the wrappedError and xerrors.New constructor.

I agree that it may be a useful feature but I'm not sure how to implement this. The fprint function, which is responsible for the formatting of errors, doesn't know if an error contains a stack trace or not, it delegates formatting to the error itself. It checks only if the error implements the DetailedError interface and if so, it uses it. To print a simple error, without stack traces, the good old Error() method may be used.

If you have an idea on how to implement this feature, your suggestion is more than welcome!