Abhinav Rai

Errors in GoLang?

Its been really long since I have written anything so I would write few things everyday. Whatever I am learning and have learnt. These will be followed by a series of blogs on Android (Something which I’ve been doing since quite some time) - Best practices, Dependency Injection, Making API calls, RXJava, FormBuilder library and much more. For today, I will write about Errors in go.

Errors are an integral part in the application but deciding how to propagate error in Go is a difficult task. Generally every other function returns an error (Thanks to multiple return in go). So we have to categorise every error and without proper planning its gonna be troublesome. For every request generally we -

  1. have to check for authentication. (401)
  2. have to check if route is correct (404)
  3. have to perform some db action — Which may or may not result in error. (500)

In go, error is an interface and we can make specific error which implement this. So instead of returning normal error object, we can return our new error which has much more information.

Lets take an example for Request not found.

type RequestNotFoundError struct {
    BaseError
    Cause error
}
type BaseError struct {
    Code string 
    Message string
    Description string
}

RequestNotFoundError should implement error.

func (rnf RequestNotFoundError) Error string {
    return fmt.Sprintf("%s\nLongDesc: %s", err.Message,   err.Description)
}
func (err BaseError) ErrorCode() string {
	return err.Code
}
// NewNotFoundError wraps original error with optional detailed messages and gives NotFoundError
func NewNotFoundError(cause error, msg ...string) NotFoundError {
	nferr := NotFoundError{
		BaseError: BaseError{
			Code:        NotFoundErrorCode,
			Message:     "Not Found",
			Description: "Request entity dosen't exist",
		},
		Cause: cause,
	}
	if len(msg) > 0 {
		nferr.Description = strings.TrimSpace(strings.Join(msg, ", "))
	}
	return nferr
}

Now this will be used in handler or anywhere the errors are needed. Just define all your error in ./pkg/error directory in errors package in go. And then use the error.

		student, err := studentStore.Find(studentID)
		if err != nil {
			handleError(err, w, r)
			return
		}

		ticketCommunicationChannel, err := studentStore.FindTicketCommunicationChannel(article)
		if err != nil {
			if _, ok := err.(errors.NotFoundError); !ok {
				handleError(err, w, r)
				return
			}
		}

		relatedStudent, err := studenteStore.FindRelatedStudent(studentID)
		if err != nil {
			handleError(err, w, r)
		}

Above is my handler code, which delegates all errors to a function handleError which handles all the errors. The err in line 1 will be produed in store by NewNotFoundError as shown below.

if result.RecordNotFound() {
    return nil, errors.NewNotFoundError(result.Error, "Article not found")
}

Below is the handleError function.

switch err.(type) {
	case errors.InternalError:
		inerr := err.(errors.InternalError)
		errLogger.Errorf("%s (cause: %s)", inerr, inerr.Cause)
		httpErr = httpError{
			BaseError:      inerr.BaseError,
			HTTPStatusCode: http.StatusInternalServerError,
		}

	case errors.NotFoundError:
		nferr := err.(errors.NotFoundError)
		errLogger.Errorf("%s (Cause %s)", nferr, nferr.Cause)
		httpErr = httpError{
			BaseError:      nferr.BaseError,
			HTTPStatusCode: http.StatusNotFound,
		}
}

So handle errors smartly. It would be difficult to write sustainably without them.