Error Handling in Go #16
sudo-suhas
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Proposing some changes to the error handling practices as part of this discussion.
Links
Repo: https://github.com/sudo-suhas/xgo
Usage docs: https://github.com/sudo-suhas/xgo/tree/master/errors
API Reference: https://pkg.go.dev/github.com/sudo-suhas/xgo/errors
The basics
To facilitate error construction, the package provides a function,
errors.E
that takes at least 1 constructor option:And this is how it may look in the most common case:
You can optionally pass in more than 1 constructor option to associate additional data with the error.
What are the problems?
The ubiquitous way of creating errors with
errors.New
or wrapping errors withfmt.Errorf
have inherent limitations:json.RawMessage
in an error. This can later be logged in a structured format.How does it solve the problems?
Associating error information
The library broadly allows for associating the following optional information with the error:
errors.WithOp
: The event or operation resulting in an error, most commonly represented bypkgname.FunctionName
. This builds up to the sequence of logical steps, a logical stack trace, if you will. This is also important because the same leaf function can be called from multiple paths and being able to capture the sequence is quite useful for debugging.errors.Kind
: This type implements theerrors.Option
interface and can be passed in as a constructor option directly. The kind will determine the classification of the error, ex: permission error, timeout error etc. The responsibility of deciphering the failure from a lower level API is best left to the layer that integrated it. The same can be inspected usingerrors.WhatKind
.errors.WithText
/errors.WithTextf
: Arbitrary string to add context to the error.errors.WithUserMsg
: A message to be returned back to the user. The error message originating from libraries is not appropriate for showing to the user. This optional constructor option can be used to associate the user message we want to return at the service level. The same message can then be retrieved usingerrors.UserMsg
while trying to construct the response for the caller.errors.WithData
: Associate arbitrary data with the error. Example: Inerrors.WithResp
, this is used to add the JSON response to the error without converting it into a string.An example:
Being able to associate these types of information allows us to do the following:
*error.Error.Details()
since it has all the relevant information, preferably near the end of processing for a request or operation.Handle errors based on attributes
Rather than writing code dependent on specific error values or types, it would be better to rely on the classification or kind of error. In the majority of cases, we would pick the code branch to execute based on the classification of error. As mentioned above, the kind can be passed in as a constructor option. Inspecting the error is quite straightforward:
Additionally, more often than not, the type of error at the site of construction determines the response being sent back to the client. And the site at which the error originated is the most qualified to identify and associate the kind of the error. For example, let's say you tried to fetch a resource from your database as part of a GET call but it was not found. Your response is likely going to be 404 or an equivalent. In such a case, based on the sentinel error defined in the database's package, the kind could be set as
errors.NotFound
.Sugar
Additional cherries on top.
Hook into the constructor
You can write custom constructor options that can take arbitrary data and translate that into 1 or more fields for the error.
Here is another example for parsing information from a HTTP response while constructing the error -
errors.WithResp
.Interop with errors.{Is, As}
The
Error
type implements the interface required by the standard library'sUnwrap
method - xgo@v0.2.0/errors/error.go#L110
. This means that wrapped errors can still be checked witherrors.{Is, As}
if required.Bring your own kind
Apart from the error kinds declared in the library it is possible and encouraged to declare custom ones if and when needed. An example, as seen here:
Bring your own error
The error package defines and uses interfaces/traits such as
errors.GetKind
,errors.StatusCoder
to allow interoperability with custom error definitions. This means that while using the errors package, you aren't locked into the types declared by it; you can declare and use custom ones if required.HTTP Interop
xgo@
master/errors#http-interop
Tell it how to JSON
There is a default implementation provided for converting the error into a serializable struct - xgo@
v0.2.0/errors/err_json.go#L19
. But this can be overridden by usingerrors.WithToJSON
. An example:What's missing
Credits
See https://github.com/sudo-suhas/xgo#credits.
Beta Was this translation helpful? Give feedback.
All reactions