Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrapping original issue in rest.Error #269

Open
Dragomir-Ivanov opened this issue Apr 19, 2020 · 7 comments
Open

Wrapping original issue in rest.Error #269

Dragomir-Ivanov opened this issue Apr 19, 2020 · 7 comments

Comments

@Dragomir-Ivanov
Copy link
Contributor

Dragomir-Ivanov commented Apr 19, 2020

There is an rest.Error type that captures all unrecognized errors from Hooks:

	if err = rsrc.Update(ctx, item, original); err != nil {
		e = NewError(err)
		return e.Code, nil, e
	}

func NewError(err error) *Error {
switch err {
...
default:
		return &Error{520, err.Error(), nil}
	}
}

However sometimes, enriched errors are used(preserving call-trace), that become useless once stringed through err.Error(). Can we preseve original error like this:

// Error defines a REST error with optional per fields error details.
type Error struct {
	// Code defines the error code to be used for the error and for the HTTP
	// status.
	Code int
	// Message is the error message.
	Message string
	// Issues holds per fields errors if any.
	Issues map[string][]interface{}
	Err    error // Or even Errs []error
}

So I can use the errors in func (r ResponseFormatter) FormatError( and send them to say Sentry or logs.

This might be a breaking change if someone is not following go vet recommendations(like in rest-layer source code :)

@smyrman
Copy link
Collaborator

smyrman commented Apr 20, 2020

We could maybe change the conversion of errors to rest.Error to rely on errors.As (new in Go 1.13).

Then you can implement your own error types as needed, and implement the following method to asign itself to a rest.Error:

func (err MyError) As(target interface{}) bool {
    v, ok := target.(*http.Error)
    if !ok { return false }
    // set v. values
    return true
}

We could for conveniences provide something like this in the rest package:

type codeError struct{
   code int
   err error
}

func StatusError(code int, err error) error {
     return codeError{code: code, err: err}
}

func (err codeError) Unwrap() error {
    return err.err
}

func (err codeError) Error() string {
    if err.err == nil {
        return http.StatusText(err.code)
    }
    return err.Error()
}


func (err codeError) As(target interface{}) bool {
    v, ok := target.(*http.Error)
    if !ok { return false }
    v.Code = err.Code()
    v.Message = err.Error()
    return true

If we do the conversion to rest.Error in the error formatter, then your "original" error could be logged to sentry first.

@smyrman
Copy link
Collaborator

smyrman commented Apr 20, 2020

There are other ways to do this, this is just one method.

@Dragomir-Ivanov
Copy link
Contributor Author

Thank you @smyrman for the detailed response. Let me think about it a little.

@smyrman
Copy link
Collaborator

smyrman commented Apr 22, 2020

Likewise we can match against errors from resource. (e.g. resource.ErrNotFound) using errors.Is, so that we will match even when they are wrapped by other errors, and thus be able to get a rest.Error instance with more context in the error message.

To wrap an error in the most plain way possible, you can use e.g. fmt.Errorf("prefix: %w", resource.ErrNotFound) or fmt.Errorf("%w: suffix", resource.ErrNotFound) in hooks etc.

Should change relevant places where errors are returned to return non-pointer errors.

@Dragomir-Ivanov
Copy link
Contributor Author

Actually I am using another error package for hooks: github.com/rotisserie/eris, which can record file:location for each error invocation/wrap, thus getting exhaustive traces for debugging.
Maybe we can still migrate rest-later to Go 1.13 errors, on order to propagate errors from hooks.

@Dragomir-Ivanov
Copy link
Contributor Author

Hi @smyrman , sorry for the big delay. Regarding this issue, can you take a look at this patch:
Dragomir-Ivanov@2e9eb16

It seems to work, without introducing new types. We can change the NewError function name if you like.

@smyrman
Copy link
Collaborator

smyrman commented May 14, 2020

Which issue does this patch solve? It seams to return exactly the same as before, excpet that the NewError function returns two values, where one is now a generic error type?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants