How to use the error interface - go

I have source like the following:
type Record struct {
Message string `json:"message"`
Service string `json:"service"`
Success bool `json:"success"`
Error string `json:"error"`
}
func (zp *Zephyr) Write(err ...*error) {
if len(err) > 0 {
errPtr := err[0]
if errPtr != nil && *errPtr != nil {
// error occurred, set success to false and Error to the error message
zp.Success = false
zp.Error = errPtr
} else {
zp.Success = true
}
}
}
What I don't understand is how can I access the string that is embedded in errPtr?

First, you likely don't want *error, you most likely just want error; pointers to interfaces are very seldom the correct choice.
Second, there isn't necessarily a string embedded in an error. The definition of error is nothing more than:
type error interface {
Error() string
}
Meaning if you call the Error() method, it will return a string, but it may be generated every time the method is called; it's not necessarily a string field in the error object.
Most likely, what you want is something like this:
func (zp *Zephyr) Write(err ...error) {
if len(err) > 0 {
errPtr := err[0]
if errPtr != nil {
// error occurred, set success to false and Error to the error message
zp.Success = false
zp.Error = errPtr.Error()
} else {
zp.Success = true
}
}
}
If you can't change the signature, you just need to dereference the pointer:
zp.Error = (*errPtr).Error()
Playground example here: https://play.golang.org/p/dxT108660l
Errors are also covered in the Go tour.

Related

Applying `errors.Is` and `errors.As` on custom made struct errors

package main
import (
"errors"
"fmt"
)
type myError struct{ err error }
func (e myError) Error() string { return e.err.Error() }
func new(msg string, args ...any) error {
return myError{fmt.Errorf(msg, args...)}
}
func (e myError) Unwrap() error { return e.err }
func (e myError) As(target any) bool { return errors.As(e.err, target) }
func (e myError) Is(target error) bool { return errors.Is(e.err, target) }
func isMyError(err error) bool {
target := new("")
return errors.Is(err, target)
}
func asMyError(err error) (error, bool) {
var target myError
ok := errors.As(err, &target)
return target, ok
}
func main() {
err := fmt.Errorf("wrap: %w", new("I am a myError"))
fmt.Println(isMyError(err))
fmt.Println(asMyError(err))
err = fmt.Errorf("wrap: %w", errors.New("I am not a myError"))
fmt.Println(isMyError(err))
fmt.Println(asMyError(err))
}
I expected
true
I am a myError true
false
I am not a myError false
but I got
false
I am a myError true
false
%!v(PANIC=Error method: runtime error: invalid memory address or nil pointer dereference) false
I tried to add
func (e myError) Unwrap() error { return e.err }
func (e myError) As(target any) bool { return errors.As(e.err, target) }
func (e myError) Is(target error) bool { return errors.Is(e.err, target) }
I tried
func asMyError(err error) (error, bool) {
target := &myError{} // was 'var target myError' before
ok := errors.As(err, &target)
return target, ok
}
I tried
func new(msg string, args ...any) error {
return &myError{fmt.Errorf(msg, args...)} // The change is the character '&'
}
but none of these changed anything. I also tried
func asMyError(err error) (error, bool) {
target := new("") // // was 'var target myError' or 'target := &myError{}' before
ok := errors.As(err, &target)
return target, ok
}
and then I got
false
wrap: I am a myError true
false
wrap: I am not a myError true
, which I guess makes sense but again does not solve my problem. I have a hard time to wrap my head this problem. Can you give me a hand?
So the point of errors.Is and errors.As is that errors can be wrapped, and these functions allow you to extract what the underlying cause of a given error was. This essentially relies on certain errors having specific error values. Take this as an example:
const (
ConnectionFailed = "connection error"
ConnectionTimedOut = "connection timed out"
)
type PkgErr struct {
Msg string
}
func (p PkgErr) Error() string {
return p.Msg
}
func getError(msg string) error {
return PkgErr{
Msg: msg,
}
}
func DoStuff() (bool, error) {
// stuff
err := getError(ConnectionFailed)
return false, fmt.Errorf("unable to do stuff: %w", err)
}
Then, in the caller, you can do something like this:
_, err := pkg.DoStuff()
var pErr pkg.PkgErr
if errors.As(err, &pErr) {
fmt.Printf("DoStuff failed with error %s, underlying error is: %s\n", err, pErr)
}
Or, if you only want to handle connection timeouts, but connection errors should instantly fail, you could do something like this:
accept := pkg.PkgErr{
Msg: pkg.ConnectionTimedOut,
}
if err := pkg.DoStuff(); err != nil {
if !errors.Is(err, accept) {
panic("Fatal: " + err.Error())
}
// handle timeout
}
There is, essentially, nothing you need to implement for the unwrap/is/as part. The idea is that you get a "generic" error back, and you want to unwrap the underlying error values that you know about, and you can/want to handle. If anything, at this point, the custom error type is more of a nuisance than an added value. The common way of using this wrapping/errors.Is thing is by just having your errors as variables:
var (
ErrConnectionFailed = errors.New("connection error")
ErrConnectionTimedOut = errors.New("connection timed out")
)
// then return something like this:
return fmt.Errorf("failed to do X: %w", ErrConnectionFailed)
Then in the caller, you can determine why something went wrong by doing:
if error.Is(err, pkg.ErrConnectionFailed) {
panic("connection is borked")
} else if error.Is(err, pkg.ErrConnectionTimedOut) {
// handle connection time-out, perhaps retry...
}
An example of how this is used can be found in the SQL packages. The driver package has an error variable defined like driver.ErrBadCon, but errors from DB connections can come from various places, so when interacting with a resource like this, you can quickly work out what went wrong by doing something like:
if err := foo.DoStuff(); err != nil {
if errors.Is(err, driver.ErrBadCon) {
panic("bad connection")
}
}
I myself haven't really used the errors.As all that much. IMO, it feels a bit wrong to return an error, and pass it further up the call stack to be handled depending on what the error exactly is, or even: extract an underlying error (often removing data), to pass it back up. I suppose it could be used in cases where error messages could contain sensitive information you don't want to send back to a client or something:
// dealing with credentials:
var ErrInvalidData = errors.New("data invalid")
type SanitizedErr struct {
e error
}
func (s SanitizedErr) Error() string { return s.e.Error() }
func Authenticate(user, pass string) error {
// do stuff
if !valid {
return fmt.Errorf("user %s, pass %s invalid: %w", user, pass, SanitizedErr{
e: ErrInvalidData,
})
}
}
Now, if this function returns an error, to prevent the user/pass data to be logged or sent back in any way shape or form, you can extract the generic error message by doing this:
var sanitized pkg.SanitizedErr
_ = errors.As(err, &sanitized)
// return error
return sanitized
All in all though, this has been a part of the language for quite some time, and I've not seen it used all that much. If you want your custom error types to implement an Unwrap function of sorts, though, the way to do this is really quite easy. Taking this sanitized error type as an example:
func (s SanitizedErr) Unwrap() error {
return s.e
}
That's all. The thing to keep in mind is that, at first glance, the Is and As functions work recursively, so the more custom types that you use that implement this Unwrap function, the longer the actual unwrapping will take. That's not even accounting for situations where you might end up with something like this:
boom := SanitizedErr{}
boom.e = boom
Now the Unwrap method will simply return the same error over and over again, which is just a recipe for disaster. The value you get from this is, IMO, quite minimal anyway.

Why do I get "Composite Literal Uses Unkeyed" error?

I'm relatively new to Go and am working on building out a request decoder. The request comes in JSON format and we decode that to a map[string]interface{}. We then pass that object data in to be decoded to our own ProcessRequest struct. As I said I'm new so I am reusing some logic in similar parts of the code wrote by previous developers. Essentially we are checking the map for the necessary pieces and then setting and returning those. Can someone explain to me why I am getting the titled error? Would I have to set the items all the way down to base structs that no longer have any nested? Is there a better way to accomplish what I want? Here is the code and the related structs. It is highlighting the error on the return of the model.ProcessRequest. TYIA
type ProcessRequest struct {
RequestID string
Message *message.Message
Rule *Rule
Options *ProcessOptions
//TODO: Context EvaluatorContext
//TODO: Links
}
type Message struct {
ID int
Key string
Source string
Headers *HeaderSet
Categories *CategorySet
Properties *PropertySet
Streams *StreamSet
}
type RuleAction struct {
Name string
Expression map[string]interface{}
}
type RuleLink struct {
LinkID int
Condition map[string]interface{}
TargetRuleID int
}
type Rule struct {
Name string
Code string
Actions []RuleAction
Links []RuleLink
}
type object = map[string]interface{}
func DecodeProcessRequest(dataObject map[string]interface{}) (*model.ProcessRequest, error) {
var (
requestID string
message *message.Message
rule *model.Rule
options *model.ProcessOptions
err error
)
if reqIDSrc, ok := dataObject["requestId"]; ok {
if requestID, err = converter.ToString(reqIDSrc); err != nil {
return nil, errors.Wrapf(err, "Error reading property 'requestID'")
}
if requestID == "" {
return nil, errors.Errorf("Property 'requestID' is an empty string")
}
} else {
return nil, errors.Errorf("Missing required property 'requestID'")
}
if messageSrc, ok := dataObject["message"]; ok {
messageData, ok := messageSrc.(object)
if !ok {
return nil, errors.Errorf("Error reading property 'message': Value is not an object")
}
if message, err = DecodeMessage(messageData); err != nil {
return nil, errors.Wrapf(err, "Error reading property 'message'")
}
} else {
return nil, errors.Errorf("Missing required property 'message'")
}
if ruleSrc, ok := dataObject["rule"]; ok {
ruleObj, ok := ruleSrc.(object)
if !ok {
return nil, errors.Errorf("Error reading property 'rule': Value is not an object")
}
if rule, err = DecodeRule(ruleObj); err != nil {
return nil, errors.Wrapf(err, "Error reading 'rule' during decoding")
}
} else {
return nil, errors.Errorf("Missing required property 'requestID'")
}
// Parse plain object to a Message struct
return &model.ProcessRequest{
requestID,
message,
rule,
options,
}, nil
}
super said in this comment:
In general, the warning says that you should prefer to use the syntax ProcessRequest{ RequestID: requestID, ... }. Naming the keys instead of unkeyed values.
That worked for me. Also the explanation by kostix in this comment really helped.
Basically the idea is that if you use "unkeyed" way of defining struct literals, the meaning of your definitions depends on the way the fields of the underlying type are layed out. Now consider that your type has three fields of type string in a certain order. Now a couple of iterations down the road some programmer moves the second field to the 1st position—your literal will still compile but will end up defining a completely different value at runtime.

How to design an individualized error, and then test for it (as opposed to the description in the error)? [duplicate]

This question already has answers here:
Compare error message in golang
(2 answers)
Closed 1 year ago.
Context: I am an amateur dev coming from Python and I am just starting with Go
(Python background to show how I manage errors today)
In Python, the typical way to raise exceptions (errors) with your own "error type" is
class noMoreBeer(Exception):
pass
try:
a_function()
except noMoreBeer as e:
print("there is no more beer ({e}), go buy some")
except Exception as e:
print(f"unexpected exception happened, namely {e}")
else:
print("thinks went fine")
The main part I would like to port to Go philosophy is that I created my own exception which can have optional explanation text, but I check for noMoreBeer, not the error text.
Now back to Go, I read several pages on how to handle errors (while I was annoyed first, I now find that it makes for better code), among them on the Go Blog. Out of this I tried to replicate the above, but the code below does not work (JetBrain's Goland points to return Error.noMoreBeer() and if err == Error.noMoreBeer())
package main
import "fmt"
type Error interface {
error
noMoreBeer() bool
}
func thisFails() Error {
// returns my specific error, but could also percolate some other errors up
return Error.noMoreBeer()
}
func main() {
err := thisFails()
if err != nil {
if err == Error.noMoreBeer() {
fmt.Println("go buy some beer")
} else {
panic("something unexpected happened")
}
}
}
Is there a way in Go to create such specific errors?
One of the main drivers for them in my case is that I do not rely on the text passed in the error, but on a [class|whatever] which, if it has a typo, will be an error.
There's four functions you should look into. Here's the official Go blog post for this subject. It was introduced in Go 1.13.
fmt.Errorf: Create a new error with some details. Can wrap errors with %w.
errors.New: Create a new error type that can be wrapped and compared with functions introduced in Go 1.13.
errors.Is: Compare an error variable with an error type. It can unwrap errors.
errors.As: Compare an error variable with an error interface implementation. It can unwrap errors.
My first shot at a solution:
package main
import (
"errors"
"fmt"
)
var noMoreBeer = errors.New("no more beer")
func thisFails() error {
// returns my specific error, but could also percolate some other errors up
return noMoreBeer
}
func main() {
err := thisFails()
if err != nil {
if err == noMoreBeer {
fmt.Println("go buy some beer")
} else {
panic("something unexpected happened")
}
}
}
The key point I understood reading https://blog.golang.org/go1.13-errors is that I can test against a variable which type is error. This is functionally equivalent to the Python example I started with.
You could use a type switch.
package main
import "fmt"
// the interface
type NoMoreBeerError interface {
noMoreBeer() bool
}
// two different implementations of the above interface
type noMoreBeerFoo struct { /* ... */ }
type noMoreBeerBar struct { /* ... */ }
func (noMoreBeerFoo) noMoreBeer() bool { return true }
func (noMoreBeerBar) noMoreBeer() bool { return false }
// have the types also implement the standard error interface
func (noMoreBeerFoo) Error() string { return " the foo's error message " }
func (noMoreBeerBar) Error() string { return " the bar's error message " }
func thisFails() error {
if true {
return noMoreBeerFoo{}
} else if false {
return noMoreBeerFoo{}
}
return fmt.Errorf("some other error")
}
func main() {
switch err := thisFails().(type) {
case nil: // all good
case NoMoreBeerError: // my custom error type
if err.noMoreBeer() { // you can invoke the method in this case block
// ...
}
// if you need to you can inspect farther for the concrete types
switch err.(type) {
case noMoreBeerFoo:
// ...
case noMoreBeerBar:
// ...
}
default:
// handle other error type
}
}
We have been using errors by cutome implementation of error interface in production without any issues. This is helping detect the error and find root cause more efficiently.
package errors
type Error struct {
err error // the wrapped error
errType ErrorType
errInfo ErrorInfo
op Op
// domain specific data
userID int
requestID string
}
type (
ErrorType string
ErrorInfo string
Op string
)
var NoMoreBeer ErrorType = "NoMoreBeerError"
func (e Error) Error() string {
return string(e.errInfo)
}
func Encode(op Op, args ...interface{}) error {
e := Error{}
for _, arg := range args {
switch arg := arg.(type) {
case error:
e.err = arg
case ErrorInfo:
e.errInfo = arg
case ErrorType:
e.errType = arg
case Op:
e.op = arg
case int:
e.userID = arg
case string:
e.requestID = arg
}
}
return e
}
func (e Error) GetErrorType() ErrorType {
return e.errType
}
By this, you can use errors as you'd use them but adding the capabilities of a expansion as needed.
e := errors.Encode(
"pkg.beerservice.GetBeer",
errors.NoMoreBeer,
errors.ErrorInfo("No more beer available"),
).(errors.Error)
switch e.GetErrorType() {
case errors.NoMoreBeer:
fmt.Println("no more beer error")
fmt.Println(e.Error()) // this will return "No more beer available"
}
Working example on playground: https://play.golang.org/p/o9QnDOzTwpc

How to detect custom errors

Motivation
I have custom error type named CustomError and I want to write a method that parses any type of error into my error structure. So I wrote parse method to show you. I will use the parse method on any function that returns error interface. So all errors will be structured with my type.
Problem
When I use parse method with nil value to return error interface, the returning error is not nil. Code is below. First test succeeded but second did not.
const (
UnHandledError = "UnHandledError"
CircuitBreakerError = "CircuitBreakerError"
)
type CustomErrorType struct {
Key string
Message string
Status int
}
func (c *CustomErrorType) Error() string {
return "Custom error message"
}
func parse(err error) *CustomErrorType {
if err == nil {
return nil
}
if e, ok := err.(*CustomErrorType); ok {
return e
}
if e, ok := err.(hystrix.CircuitError); ok {
return &CustomErrorType{CircuitBreakerError, e.Message, http.StatusTooManyRequests}
}
return &CustomErrorType{UnHandledError, err.Error(), http.StatusInternalServerError}
}
func TestParse_it_should_return_nil_when_error_is_nil(t *testing.T) {
result := parse(nil)
if result != nil {
t.Error("result is not nil")
}
}
func TestParse_it_should_return_nil_when_error_is_nil_2(t *testing.T) {
aFunc := func() error {
return parse(nil)
}
result := aFunc()
if result != nil {
t.Error("result is not nil")
}
}
Can you explain what am I missing or what is wrong?
This is an instance of a common "problem" of go's interfaces that is caused by the actual implementation of interfaces under the hood: an interface containing a nil pointer is not-nil.
it is described in go's faq with an example that resembles your situation with the error interface: Why is my nil error value not equal to nil?
Under the covers, interfaces are implemented as two elements, a type T and a value V. V is a concrete value such as an int, struct or pointer, never an interface itself, and has type T.
...
An interface value is nil only if the V and T are both unset, (T=nil, V is not set), In particular, a nil interface will always hold a nil type. If we store a nil pointer of type *int inside an interface value, the inner type will be *int regardless of the value of the pointer: (T=*int, V=nil). Such an interface value will therefore be non-nil even when the pointer value V inside is nil.
This situation can be confusing, and arises when a nil value is stored inside an interface value such as an error return:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
this example is similar to what is happening in your code when you do:
aFunc := func() error {
return parse(nil)
}
parse() returns *CustomErrorType, but the function just error making the value returned an interface that contains a type and a nil value: (T=*CustomErrorType, V=nil) that in turn evaluates to not-nil.
the faq then goes on providing explanation and showing a "correct" example:
If all goes well, the function returns a nil p, so the return value is an error interface value holding (T=*MyError, V=nil). This means that if the caller compares the returned error to nil, it will always look as if there was an error even if nothing bad happened. To return a proper nil error to the caller, the function must return an explicit nil:
func returnsError() error {
if bad() {
return ErrBad
}
return nil
}
the behavior can also be observed in your example adding a
fmt.Printf("%#v\n", result)
to print result's value:
(*e.CustomErrorType)(nil)
if we change parse() return type to just error it will print:
<nil>

How do I cleanly separate user-facing errors from internal errors in Golang?

I am running into a common pattern where I have a function that takes user input, and either returns a successful output value or an error. But it can return different types of errors, some of which are the result of bad user input and others which are the result of internal errors (DB unavailable, for instance).
My functions have signatures that look like this:
// ProcessInput takes a user-input string and returns a processed value
func ProcessInput(input string) (ProcessedValue, error, error) {}
The first return value is meaningful (not nil) if no errors were encountered, the second return value is an error if the user input failed validation, and the third return value is an error if an unexpected internal error occurred.
This works fine but it does not feel very clean, and it's not obvious from looking at the signature what the different errors are. I considered naming the error returns but I don't like the side-effects of naming return parameters.
Are there any other patterns I could apply that are cleaner? Should I have a single error return and distinguish by type on the caller side?
Returning multiple error does not seem very Go-like to me.
Why not have an interface called UserError that defines a method that returns a message suitable to show the user. If the returned error does not implement UserError, show a standard "Internal Server Error" message. Example:
type UserError interface {
error
UserError() string
}
type emptyInput struct {}
func (e emptyInput) Error() string {
return e.UserError()
}
func (emptyInput) UserError() string {
return "Empty input"
}
func ProcessInput(input string) (*ProcessedValue, error) {
if input == "" {
return nil, &emptyInput{}
}
}
func httpHandler() {
val, err := ProcessInput(input)
if err != nil {
if userErr := err.(UserError); userErr != nil {
// show userError.UserError() to user
} else {
// Log error
// show Internal server error message
}
}
}
Should I have a single error return and distinguish by type on the
caller side?
That's what I would recommend. You could create a global error variables for different types of errors. And caller can check what kind of error was returned.
var ErrValidation = fmt.Errorf("Validation failed.")
func ProcessInput(input string) (ProcessedValue, error) {
if !validate(input) {
return nil, ErrValidation
}
// process stuff and return
}
And on caller side:
value, err := ProcessInput(input)
if err != nil {
if err == ErrValidation {
// Tell user validation failed
} else {
// Show internal server error message
}
}
// do things with value

Resources