goapperrors

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 18, 2024 License: MIT Imports: 7 Imported by: 0

README

Go Version GoDoc Build Status Coverage Status GoReport

Manipulating application errors with ease

Functionalities

  • Provides AppError type to be used for wrapping any kind of errors in an application
  • Supports a centralized definition of errors
  • AppError can carry extra information such as cause, debug log, and stack trace
  • AppError supports translating its error message into a specific language
  • AppError can be transformed to a JSON structure which is friendly to client side
  • Provides MultiError type to handle multiple AppError(s) with a common use case of validation errors

Installation

go get github.com/tiendc/go-apperrors

Usage

Basic
  • Initializes go-apperrors at program startup
import gae "github.com/tiendc/go-apperrors"

func main() {
    ...
    gae.Init(&gae.Config{
        Debug: ENV == "development",
        DefaultLogLevel: gae.LogLevelInfo,
        TranslationFunc: func (gae.Language, key string, params map[string]any) {
            // Provides your implementation to translate message
        },
    })
    ...
}
  • Defines your errors
// It is recommended to add a new directory for placing app errors.
// In this example, I use `apperrors/errors.go`.

import gae "github.com/tiendc/go-apperrors"

// Some base errors
var (
    ErrInternalServer = gae.Create("ErrInternalServer", &gae.ErrorConfig{
        Status: http.StatusInternalServerError,
        LogLevel: gae.LogLevelError, // this indicates an unexpected error
    })
    ErrUnauthorized = gae.Create("ErrUnauthorized", &gae.ErrorConfig{Status: http.StatusUnauthorized})
    ErrNotFound = gae.Create("ErrNotFound", &gae.ErrorConfig{Status: http.StatusNotFound})
    ...
)

// Errors from external libs
var (
    ErrRedisKeyNotFound = gae.Add(redis.Nil, &gae.ErrorConfig{Status: http.StatusNotFound})
)

// Some more detailed errors
var (
    ErrUserNotInProject = gae.Create("ErrUserNotInProject", &gae.ErrorConfig{Status: http.StatusForbidden})
    ...
)
  • Handles errors in your main processing code
// There are some use cases as below.

// 1. You get an unexpected error
// Just wrap it and return. This will result in error 500 returned to client.
resp, err := updateProject(project)
if err != nil {
    return gae.New(err).WithDebug("extra info: project %s", project.ID)
    // OR `return gae.Wrap(err)` if you don't need to add extra info
}

// 2. You get an error which may be expected
resp, err := deleteProject(project)
if err != nil {
    if errors.Is(err, DBNotFound) { // this error can be expected
        return gae.New(gae.ErrNotFound).WithCause(err)
    }
    return gae.Wrap(err) // unexpected error
}

// 3. You want to return an error when a condition isn't satisfied
if `user.ID` is not in `project.userIDs` {
    // This will return error Forbidden to client as we configured previously
    return gae.New(gae.ErrUserNotInProject)
}
  • Handles validation errors
// Validation is normally performed when you parse requests from client.
// You may use an external lib for the validation. That's why you need to make
// `adapter` code to transform the validation errors to `AppError`s.

// Add a new file to the above directory, says `apperrors/validation_errors.go`.
// This adapter function will be used later to create validation error.
func ValidationErrorInfoBuilder(appErr AppError, buildCfg *gae.InfoBuilderConfig) *gae.InfoBuilderResult {
    // Extracts the inner validation error
    vldErr := &thirdPartyLib.Error{}
    if !errors.As(appErr, &vldErr) {
        // panic, should not happen
    }

    return &gae.InfoBuilderResult{
        // Transform the error from the 3rd party lib to ErrorInfo struct
        ErrorInfo: &gae.ErrorInfo{
            Message: buildCfg.TranslationFunc(buildCfg.Language, vldErr.getMessage(), appErr.Params()),
            Source: vldErr.getSource(),
            ...
        }
    }
}

// When parse your request
func (req UpdateProjectReq) Validate() gae.ValidationError {
    vldErrors := validateReq(req)
    return gae.NewValidationErrorWithInfoBuilder(apperrors.ValidationErrorInfoBuilder, vldErrors...)
}
  • Handles errors before returning them to client
// In the base handler, implements function `RenderError()`
func RenderError(err error, requestWriter Writer) {
    // Gets language from request, you can use util `gae.ParseAcceptLanguage()`
    lang := parseLanguageFromRequest()

    // Call goapperrors.Build
    buildResult := gae.Build(err, lang)

    // Log error to Sentry or a similar service
    if buildResult.ErrorInfo.LogLevel != gae.LogLevelNone {
        logErrorToSentry(err, buildResult.ErrorInfo.LogLevel)
    }

    // Sends the error as JSON to client
    requestWriter.SendJSON(buildResult.ErrorInfo)
}

// In your specific handler, for instance, project handler
func (h ProjectHandler) UpdateProject(httpReq) {
    req, err := parseAndValidateRequest(httpReq)
    if err != nil {
        baseHandler.RenderError(err)
        return
    }

    resp, err := useCase.UpdateProject(req)
    if err != nil {
        baseHandler.RenderError(err)
        return
    }

    // Send result to client
    requestWriter.SendJSON(resp)
}
Configuration

TBD

Contributing

  • You are welcome to make pull requests for new functions and bug fixes.

License

Documentation

Index

Constants

View Source
const (
	LanguageEn = "en"
	LanguageFr = "fr"
	LanguageDe = "de"
	LanguageEs = "es"
	LanguageIt = "it"
	LanguagePt = "pt"
	LanguageRu = "ru"
	LanguageZh = "zh"
	LanguageJa = "ja"
	LanguageKo = "ko"
	LanguageAr = "ar"
	LanguageHi = "hi"
)

Variables

This section is empty.

Functions

func Add

func Add(err error, cfg *ErrorConfig) error

Add adds a global config mapping for a base error, then returns the error. This function is recommended for adding mapping for app-external errors.

Example:

 var ErrRedisKeyNotFound = Add(redis.Nil, &ErrorConfig{
	      Status: http.StatusNotFound,
	      Code: "ErrRedisKeyNotFound",
 })

func Create

func Create(code string, cfg *ErrorConfig) error

Create creates an error for the code with the mapping config, then returns the newly created error. This function is recommended for adding mapping for app-internal errors. When use this method, you don't need to set custom error code or translation key, they will be the same as the input code.

Example:

var ErrTokenInvalid = Create("ErrTokenInvalid", &ErrorConfig{Status: http.StatusUnauthorized})
var ErrNoPermission = Create("ErrNoPermission", &ErrorConfig{Status: http.StatusForbidden})

func GetStackTrace

func GetStackTrace(err error) []runtime.Frame

GetStackTrace gets stack trace stored in the error if there is

func Init

func Init(cfg *Config)

Init initializes global config

func ParseAcceptLanguage

func ParseAcceptLanguage(acceptLang string) ([]language.Tag, []float32, error)

ParseAcceptLanguage parses header Accept-Language from http request. This func returns a list of `Tag` objects in priority order.

Example:

Accept-Language: *
Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5

func ParseAcceptLanguageAsStr

func ParseAcceptLanguageAsStr(acceptLang string) ([]string, error)

ParseAcceptLanguageAsStr parses header Accept-Language from http request. This func returns a list of `Tag` strings in priority order.

Example:

Accept-Language: "fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5"
  gives result: ["fr-CH", "fr", "en", "mul"]

func Remove

func Remove(err error)

Remove removes the error from the global config mappings

func UnwrapMulti

func UnwrapMulti(err error) []error

UnwrapMulti unwraps en error to a slice of errors. If the error implements `Unwrap() []error`, the result of func call is returned. If the error implements `Unwrap() error`, the func is called and the result slice has only one element. If no `Unwrap` func is implemented in the error, `nil` is returned.

func UnwrapToRoot

func UnwrapToRoot(err error) error

UnwrapToRoot keeps unwrapping until the root error

func Wrap

func Wrap(err error) error

Wrap wraps an error with adding stack trace if configured

func Wrapf

func Wrapf(format string, args ...any) error

Wrapf wraps an error by calling fmt.Errorf and adds stack trace if configured

Types

type AppError

type AppError interface {
	error

	// Params gets non-translating param map
	Params() map[string]any
	// TransParams gets translating param map (values of this map will be translated)
	TransParams() map[string]string
	// Cause gets cause of the error
	Cause() error
	// Debug gets debug message
	Debug() string
	// Config returns the custom config if set, otherwise returns the global mapping one
	Config() *ErrorConfig
	// CustomConfig gets custom config associated with the error
	CustomConfig() *ErrorConfig
	// CustomBuilder gets custom info builder
	CustomBuilder() InfoBuilderFunc

	// WithParam sets a custom param
	WithParam(k string, v any) AppError
	// WithTransParam sets a custom param with value to be translated when build info
	WithTransParam(k string, v string) AppError
	// WithCause sets cause of the error
	WithCause(err error) AppError
	// WithDebug sets debug message (used for debug purpose)
	WithDebug(format string, args ...any) AppError
	// WithCustomConfig sets custom config for the error
	WithCustomConfig(*ErrorConfig) AppError
	// WithCustomBuilder sets custom info builder
	WithCustomBuilder(InfoBuilderFunc) AppError

	// Build builds error info
	Build(Language, ...InfoBuilderOption) *InfoBuilderResult
}

AppError is designed to be used as base error type for any error in an application. An AppError can carry much extra information such as `cause`, `debug log`, and stack trace. It also supports translating the message into a specific language.

func New

func New(err error) AppError

New creates an AppError containing the given error

type AppErrors

type AppErrors []AppError

AppErrors is a defined type of slice of AppError

func (AppErrors) Error

func (aes AppErrors) Error() string

Error implements `error` interface

func (AppErrors) Is

func (aes AppErrors) Is(err error) bool

Is implementation used by errors.Is()

func (AppErrors) Unwrap

func (aes AppErrors) Unwrap() []error

Unwrap implementation used by errors.Is(). errors.Unwrap() only works with `Unwrap() error`.

type Config

type Config struct {
	// Debug flag indicates debug mode (default: `false`).
	// If `false`, app error `debug` string can't be set.
	Debug bool

	// WrapFunc to wrap an error with adding stack trace (default: `nil`).
	// This function is nil by default which means the library will use default value
	// which is function Wrap from `github.com/go-errors/errors`.
	WrapFunc func(error) error
	// MaxStackDepth max stack depth (default: `50`).
	// If WrapFunc is set with custom value, this config has no effect.
	MaxStackDepth int

	// DefaultLanguage default language (default: `LanguageEn`)
	DefaultLanguage Language
	// TranslationFunc function to translate message into a specific language (default: `nil`)
	TranslationFunc TranslationFunc
	// FallbackToErrorContentOnMissingTranslation indicates fallback to error content
	// when translation failed (default: `true`).
	// If `false`, when translation fails, the output message will be empty.
	FallbackToErrorContentOnMissingTranslation bool
	// MultiErrorSeparator separator of multiple error strings (default: `\n`)
	MultiErrorSeparator string

	// DefaultErrorStatus default status for error if unset (default: `500`)
	DefaultErrorStatus int
	// DefaultValidationErrorStatus default status for validation error if unset (default: `400`)
	DefaultValidationErrorStatus int
	// DefaultValidationErrorCode default code for validation error if unset (default: `ErrValidation`)
	DefaultValidationErrorCode string

	// DefaultLogLevel default log level for errors if unset (default: `LogLevelNone`)
	DefaultLogLevel LogLevel
}

Config to provide global config for the library

type ErrorConfig

type ErrorConfig struct {
	Status   int
	Code     string
	Title    string
	LogLevel LogLevel
	TransKey string
	Extra    any
}

ErrorConfig configuration of an error to be used when build error info

func GetErrorConfig

func GetErrorConfig(err error) *ErrorConfig

GetErrorConfig gets global mapping config of an error if set

type ErrorInfo

type ErrorInfo struct {
	Status      int          `json:"status,omitempty"`
	Code        string       `json:"code,omitempty"`
	Source      any          `json:"source,omitempty"`
	Title       string       `json:"title,omitempty"`
	Message     string       `json:"message,omitempty"`
	Cause       string       `json:"cause,omitempty"`
	Debug       string       `json:"debug,omitempty"`
	LogLevel    LogLevel     `json:"logLevel,omitempty"`
	InnerErrors []*ErrorInfo `json:"errors,omitempty"`

	AssociatedError error `json:"-"`
}

ErrorInfo stores error information which can be used to return to client

type InfoBuilderConfig

type InfoBuilderConfig struct {
	ErrorConfig                                ErrorConfig
	InfoBuilderFunc                            InfoBuilderFunc
	Language                                   Language
	ErrorSeparator                             string
	TranslationFunc                            TranslationFunc
	FallbackToErrorContentOnMissingTranslation bool
}

InfoBuilderConfig config used to build error info

type InfoBuilderFunc

type InfoBuilderFunc func(AppError, *InfoBuilderConfig) *InfoBuilderResult

InfoBuilderFunc custom info builder function

type InfoBuilderOption

type InfoBuilderOption func(*InfoBuilderConfig)

InfoBuilderOption config setter for building error info

func InfoBuilderOptionCustomBuilder

func InfoBuilderOptionCustomBuilder(infoBuilderFunc InfoBuilderFunc) InfoBuilderOption

InfoBuilderOptionCustomBuilder sets custom info builder

func InfoBuilderOptionCustomConfig

func InfoBuilderOptionCustomConfig(errorConfig ErrorConfig) InfoBuilderOption

InfoBuilderOptionCustomConfig sets custom config

func InfoBuilderOptionFallbackContent

func InfoBuilderOptionFallbackContent(fallbackToContent bool) InfoBuilderOption

InfoBuilderOptionFallbackContent sets flag fallback to error content on missing translation

func InfoBuilderOptionSeparator

func InfoBuilderOptionSeparator(errorSeparator string) InfoBuilderOption

InfoBuilderOptionSeparator sets custom content separator

func InfoBuilderOptionTranslationFunc

func InfoBuilderOptionTranslationFunc(translationFunc TranslationFunc) InfoBuilderOption

InfoBuilderOptionTranslationFunc sets custom translation function

type InfoBuilderResult

type InfoBuilderResult struct {
	// ErrorInfo result information of error which can be used to return to client
	ErrorInfo *ErrorInfo
	// TransMissingKeys missing keys when translate
	TransMissingKeys []string
	// TransMissingMainKey is set `true` if the main message key is missing
	TransMissingMainKey bool
}

InfoBuilderResult result of building process

func Build

func Build(err error, lang Language, options ...InfoBuilderOption) *InfoBuilderResult

Build builds error info

type Language

type Language any

Language represents a language. Language values can be anything and can be set by client code. For example, Language("string") or Language(language.Tag from "golang.org/x/text/language").

type LogLevel

type LogLevel string

LogLevel represents log level set for an error. You can use LogLevel to report the level of an error to external services such as Sentry or Rollbar.

const (
	LogLevelNone  LogLevel = ""
	LogLevelDebug LogLevel = "debug"
	LogLevelInfo  LogLevel = "info"
	LogLevelWarn  LogLevel = "warning"
	LogLevelError LogLevel = "error"
	LogLevelFatal LogLevel = "fatal"
)

type MultiError

type MultiError interface {
	AppError

	InnerErrors() AppErrors
}

MultiError can handle multiple underlying AppErrors

func AsMultiError

func AsMultiError(err AppError) MultiError

AsMultiError converts AppError to MultiError

func NewMultiError

func NewMultiError(errs ...AppError) MultiError

NewMultiError creates a MultiError with wrapping the given errors

type TranslationFunc

type TranslationFunc func(lang Language, key string, params map[string]any) (string, error)

type ValidationError

type ValidationError MultiError

func NewValidationError

func NewValidationError(errs ...AppError) ValidationError

NewValidationError creates a validation error for the given validation error items

func NewValidationErrorWithInfoBuilder

func NewValidationErrorWithInfoBuilder(infoBuilder InfoBuilderFunc, errs ...error) ValidationError

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL