Custom Errors in Golang

· 451 words · 3 minutes read go golang error errors

In Go there is no try catch like in some other languages, errors are simply returned like any other data from a function and you check the value to see if an error occurred. While most of the time simply using something like fmt.Errorf() or errors.New() is good enough, if you want to try to act on the situation based on an error it makes sense to create a custom error type and then do a type assertion for it when checking the results of your call.

An example of this might be where you’re writing a webservice and you want to differentiate between authentication errors, and authorization errors. In the first case it can possibly be fixed by the user trying to login again, the second case however needs some sort of intervention before being fixed. In an example like this you might define your errors like this:

Errors.go:

package main

type AuthenticationError struct {
	msg string
}

func (e AuthenticationError) Error() string {
	return e.msg
}

type AuthorizationError struct {
	msg string
}

func (e AuthorizationError) Error() string {
	return e.msg
}

Because the structs implement the only function that the interface error contains, you have created a custom error type that can be returned.

When validating the users cookie you could hit either of these errors and return them to the caller. In the case of an AuthenticationError, the caller would simply forward you back to the login page. In the case of an AuthorizationError, then you would likely return an HTTP 401 HTTP Unauthorized, or possibly forward you to a ticketing system to request authorization. You would do this in the following way:

Handler.go:

package main

import "net/http"

func validateUser(cookie *http.Cookie) error {
 //validate the cookie
}

func handler(c echo.Context) error {
	err := validateUser(c.Request().Cookie("SSOCookie"))
	if err != nil {
		switch err.(type) {
		    case AuthorizationError:
		    	// return 401 unauthorized
		    case AuthenticationError:
		    	// redirect to login page
		}
	}
}

In doing something like this you can make your code more resilient. If for some reason the error structs are deleted, instead of having a bug that is hard to trace because you were validating the string value of the error, now you will get a compiler error because of the missing type which should be easy to pinpoint.

You don’t always have this type of luxury unfortunately as not everyone will create custom error types for their libraries, so unfortunately sometimes you will have to revert to string parsing when validating the error that is returned, but it will always be worth it to look into the documentation and the code for a library to validate if it contains custom error types for you to validate against.