Comparación directa de errores en Go

Es un fallo habitual, en el manejo de errores dentro de Go, realizar comparasiones de strings contra el mensaje del error que quieres reconocer, pero lo cierto es que, el modo más acertado de discernir que error estas manejando, es hacer una comparación de tipos de error dentro de una sentencia case.

¿Quiero decir entonces que puedo gestionar distintos tipos de error? Claro que sí. Fíjate que, en Go, todo lo que inplemente la interface error es un error. Además, puedes comprobar por tí mismo que es una interface muy sencilla:

type error interface {
  Error() string
}

Para darte un ejemplo voy a definir un struct, ErrPizzaIsOver, el va a contener un string llamado message:

type ErrPizzaIsOver struct {
  message string
}

Para que este struct pueda ser usado como error debe implementar la interface que te mostré más arriba, así que voy a implementarla:

func (e *ErrPizzaIsOver) Error() string {
  return e.message
}

Por último, voy a crear una función que, recibiendo como parámetro un  string, me devuelva un error del tipo ErrPizzaIsOver, y configurado con dicho string como message.

func NewErrPizzaIsOver(message string) *ErrPizzaIsOver {
  return &ErrPizzaIsOver{
    message: message,
  }
}

Todo listo, es el momento de hacer uso de nuestro nuevo tipo de error, así que vamos a ver como quedaría todo. Observa que la magia se hace con el patrón switch err.(type).

package main
import (
  "fmt"
  "os"
  "strconv"
)

type pizza struct {
  numberOfPieces int
}

type ErrPizzaIsOver struct {
  message string
}

func (e *ErrPizzaIsOver) Error() string {
  return e.message
}

func NewErrPizzaIsOver(message string) *ErrPizzaIsOver {
  return &ErrPizzaIsOver{
    message: message,
  }
}

func (p *pizza) eatAPiece() error {
  if !(p.numberOfPieces > 0) {
    return NewErrPizzaIsOver("There are no more pieces of pizza")
  } else {
    p.numberOfPieces--
    fmt.Println("Ñam, ñam...")
    return nil
  }
}

func main() {
  myPizza := &pizza{8}
  fmt.Println("There are " + strconv.FormatUint(uint64(myPizza.numberOfPieces), 10) + " pieces of pizza")
  for {
    err := myPizza.eatAPiece()
    if err != nil {
      switch err.(type) {
        case *ErrPizzaIsOver:
          fmt.Println(err.Error())
          os.Exit(0)
        default:
          fmt.Println("What the f**k just happened?")
      }
    }
        fmt.Println("Now there are " + strconv.FormatUint(uint64(myPizza.numberOfPieces), 10) + " pieces of pizza")
    }
}