Skip to content

Instantly share code, notes, and snippets.

@kkharji
Created December 12, 2023 10:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kkharji/d2608e949a3c0b940cb70afdfbf741b5 to your computer and use it in GitHub Desktop.
Save kkharji/d2608e949a3c0b940cb70afdfbf741b5 to your computer and use it in GitHub Desktop.
Errx library
// Package to provide advanced error type with the purpose of making errors
// easy to read and trace, construct and pass, wrap and extend and more.
package errx
import (
"errors"
"fmt"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap/zapcore"
)
// A unique string describing a method or a function.
// Hint: Defined at the top of method or function
type Op = string
// Error Kind (what category is this error part of)
type Kind struct{ name string }
var (
KindNotFound = &Kind{name: "Not Found"}
KindBadRequest = &Kind{name: "Bad Request"}
KindUnexpected = &Kind{name: "Unexpected"}
KindValidation = &Kind{name: "Validation"}
KindConflict = &Kind{name: "Conflict"}
)
var _ error = (*Error)(nil)
// Error Struct
type Error struct {
Op Op // Op is the operation resulted in the error
Kind *Kind // Kind is the kind of the error
Err error // Err is the wrapped error
Meta map[string]any // Meta is metadata associated with the error
Severity *zapcore.Level // Severity of the error (the lower, the more expected the error is)
}
const (
SeverityDebug zapcore.Level = zapcore.DebugLevel
SeverityInfo = zapcore.InfoLevel
SeverityWarn = zapcore.WarnLevel
SeverityError = zapcore.ErrorLevel
SeverityPanic = zapcore.PanicLevel
SeverityFatal = zapcore.FatalLevel
)
// implements error interface
func (e *Error) Error() string {
if e.Err != nil {
subErrMsg := e.Err.Error()
if subErrMsg != "" {
return fmt.Sprintf("%s: %s", e.Op, e.Err.Error())
} else {
return e.Op
}
} else {
return e.Op
}
}
// Ops returns the "stack" of operations for each generated error.
func Ops(e *Error) []Op {
operations := []Op{e.Op}
subErr, ok := e.Err.(*Error)
if !ok {
return operations
}
operations = append(operations, Ops(subErr)...)
return operations
}
// E create new Error with given parameters. Any order should be sufficient.
func E(parameters ...interface{}) error {
e := &Error{}
for _, param := range parameters {
switch param := param.(type) {
case Op:
e.Op = param
case error:
e.Err = param
case *Kind:
e.Kind = param
case *zapcore.Level:
e.Severity = param
case map[string]any:
e.Meta = param
}
}
// We want to try inherit kind or severity from wrapped error if not set
if e.Err != nil && (e.Kind == nil || e.Severity == nil) {
if subErr, ok := e.Err.(*Error); ok {
if e.Severity == nil {
e.Severity = subErr.Severity
}
if e.Kind == nil {
e.Kind = subErr.Kind
}
}
}
return e
}
// E create new Error with message and any parameters
func EMsg(msg string, parameters ...interface{}) error {
err := errors.New(msg)
parameters = append(parameters, err)
return E(parameters)
}
func IntoHTTPError(err error) error {
error, ok := err.(*Error)
if !ok {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
code := fiber.StatusInternalServerError
switch error.Kind {
case KindNotFound:
code = fiber.StatusNotFound
case KindBadRequest:
code = fiber.StatusBadRequest
case KindUnexpected:
code = fiber.StatusInternalServerError
case KindValidation:
code = fiber.StatusBadRequest
case KindConflict:
code = fiber.StatusConflict
}
ops := Ops(error)
msg := string(ops[len(ops)-1])
if msg == error.Op {
msg = error.Err.Error()
}
return fiber.NewError(code, msg)
}
// Package to provide advanced error type with the purpose of making errors
// easy to read and trace, construct and pass, wrap and extend and more.
package errx
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestE(t *testing.T) {
causeInner := E("invalid url") // a function
errorInner := E("db.connect", SeverityError, KindUnexpected, causeInner) // a function
errorOuter := E("users.get", errorInner) // finall function
assert.Equal(t, "users.get: db.connect: invalid url", errorOuter.Error())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment