william
william

Reputation: 479

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?

Upvotes: 12

Views: 1744

Answers (2)

jussius
jussius

Reputation: 3274

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

Upvotes: 2

user142162
user142162

Reputation:

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
        }
    }
}

Upvotes: 13

Related Questions