💻 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