skip to content
Alvin Lucillo

Err type and behavior as context

/ 2 min read

💻 Tech

In Go, sometimes the default error may not give proper context that’s why we create custom errors. Case in point is *net.OpError of the net package. When we have the errors, we can choose to check for error based on what it is (“type”) or what it does (“behavior”).

It’s preferred to check by behavior because we maintain the decoupled state of the error as opposed to check its concrete type. In the code below, ValidationError implements the error interface with Fatal() method. errTypeAsContext() checks the error by checking its concrete type. This is context by type. errBehaviorAsContext() checks the error by checking its behavior. This is context by behavior.

An error if not nil has an underlying concrete implementation. When we check that it implements errorFatal interface with type assertion, we determine that we can use Fatal().

Code:

package main

import "fmt"

type ValidationError struct {
	Field string
}

func (v ValidationError) Error() string {
	return fmt.Sprintf("validation: %v field has error", v.Field)
}

func (v ValidationError) Fatal() bool {
	return false
}

type errorFatal interface {
	Fatal() bool
}

func main() {
	errTypeAsContext()
	errBehaviorAsContext()

}

func errTypeAsContext() {
	err := validate()
	switch e := err.(type) {
	case *ValidationError:
		fmt.Printf("validation error, fatal %v\n", e.Fatal())
	default:
		fmt.Println("unknown error")
	}

}

func errBehaviorAsContext() {
	err := validate()
	switch e := err.(type) {
	case errorFatal:
		fmt.Printf("validation error, fatal %v\n", e.Fatal())
	default:
		fmt.Println("unknown error")
	}

}

func validate() error {
	return &ValidationError{Field: "name"}
}

Output:

validation error, fatal false
validation error, fatal false

Go Playground: https://go.dev/play/p/JVKh48KN23r