meh

package module
v1.14.0 Latest Latest
Warning

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

Go to latest
Published: Mar 18, 2024 License: MIT Imports: 5 Imported by: 97

README

meh

made-with-Go Go GitHub go.mod Go version GoReportCard example codecov GitHub issues GitHub code size in bytes

Mastery of Error Handling

Convenient error handling for Go using custom error containers and bubbling features.

This document provides an overview over provided features but is not complete. Documentation available here.

Installation

In order to use this package, run:

go get github.com/lefinal/meh

What errors are made of

Errors consist of the following properties:

Code

Error codes that also describe the severity. When an error is created, the code is set. The following default codes ship with meh:

  • internal: Basic internal errors like a failed database query.
  • bad-input: Bad user input/request.
  • not-found: The requested resource could not be found.
  • unauthorized: Authentication is required for accessing the resource or performing the action.
  • forbidden: Invalid permissions for accessing the resource or performing the action.
  • neutral: Used for wrapping errors without changing the code.
  • (unexpected): No code specified.

Codes can be used for error handling based on the occurred problem. They may also be used for choosing HTTP status codes (see the mehhttp-package).

Of course, you can define custom codes. However, you should use the __-prefix in order to avoid collisions with codes being added natively in the future. Example: __myapp_my_code

Wrapped error

Errors are meant to be wrapped when being returned to the caller. This allows details to bubble up and be logged as well as the error message to be displayed in a stacktrace-like manner. If the error is a root error (the original cause), this property will not be set.

Message

The actual error message. This is a string and should describe the action that was performed.

Bad example: error while loading file

Good example: load file

The error messages will be concatenated with colons. Therefore, the final format with the original error wrapped two times looks like this:

get users: load user file: file not found

Details

Details are one of the main reasons why meh was developed. They are of type map[string]interface{} and allow passing arbitrary details as key-value pairs. Most times you will want to pass function call arguments here in order to allow easier inspection of issues. Because of wrapping, details are persisted and returned back all to the top caller which handles the error. If no details are provided, this can be kept unset (nil).

Creating errors

Errors can be created manually using the Error-struct:

return &meh.Error{
	Code: meh.ErrInternal,
	WrappedErr: err,
	Message: "read file",
	Details: meh.Details{
		"file_name": "my-file.txt"
    }
}

However, you want to use generators most times because of the syntactic sugar they provide.

General ones:

func NewErr(code Code, message string, details Details) error
func NewErrFromErr(err error, code Code, message string, details Details) error

These allow creating a new error with the given code, message and details. The ones with FromErr-suffix create a new error with the given one used as wrapped error.

Often, you use the native error codes. That's why there are generators, including codes:

func NewInternalErr(message string, details Details) error
func NewInternalErrFromErr(err error, message string, details Details) error
func NewBadInputErr(message string, details Details) error
func NewBadInputErrFromErr(err error, message string, details Details) error
func NewNotFoundErr(message string, details Details) error
func NewNotFoundErrFromErr(err error, message string, details Details) error
func NewUnauthorizedErr(message string, details Details) error
func NewUnauthorizedErrFromErr(err error, message string, details Details) error
func NewForbiddenErr(message string, details Details) error
func NewForbiddenErrFromErr(err error, message string, details Details) error

Wrapping errors

Most of the time, you do not want to create new error but wrap it for passing it over to the caller. This preserves the error code from the underlying error.

Let's have a look at an example which describes a situation where errors are wrapped:

struct Fruit {
	// ...
}

func IncrementApplesForUser(userID uuid.UUID, includePineapples bool) error {
	apples, err := applesByUser(userID, includePineapples)
	if err!= nil {
		return meh.Wrap(err, "apples by user", meh.Details{
			"user_id": userID,
			"incude_pineapples": includePineapples,
		})
	}
	// ...
}

func applesByUser(userID uuid.UUID, includePineapples bool) (int, error) {
	fruits, err := fruitsByUser(userID)
	if err != nil {
		return 0, meh.Wrap(err, "fruits by user", meh.Details{"user_id": userID})
	}
	// ...
}

func fruitsByUser(userID uuid.UUID) ([]Fruit, error) {
	fruitsRaw, err := readFruitsFile()
	if err != nil {
		return nil, meh.Wrap(err, "read fruits file", nil)
	}
	// ...
}

func readFruitsFile() ([]byte, error) {
	const fruitsFilename = "fruits.txt"
	b, err := os.ReadFile(fruitsFilename)
	if err != nil {
		return nil, meh.NewInternalErrFromErr(err, "read fruits file", meh.Details{
			"fruits_filename": fruitsFilename,
		})
	}
	return b, nil
}

By wrapping the error, the meh.ErrInternal-code from readFruitsFile is preserved. Details are passed as well and the final error message would look like this:

apples by user: fruits by user: read fruits file: file not found

Of course, the error code could also be changed. For example, if the returned error from os.ReadFile is checked to be a os.ErrNotExist and meh.ErrNotFound is returned, fruitsByUser could then return meh.NewInternalErrFromErr(err, ...) in order to change the code to ErrInternal as readFruitsFile is expected to not fail.

Checking the error code

As already mentioned, each layer of "wrapping" is represented another meh.Error with its own code. If you want to check the actual error code, use meh.ErrorCode(err error). This will return the error code of the first error without meh.ErrNeutral-code, which is set when wrapping errors.

Logging

Documentation

Support for logging with zap comes out of the box and is provided with the package mehlog.

Set the log-level translation with mehlog.SetDefaultLevelTranslator and log with mehlog.Log. This logs the error to the level which is determined by the error code (same as meh.ErrorCode).

HTTP support

Documentation

In combination with mehlog, support for responding with the correct status code and logging error details with request details is provided. Currently, features are rather limited and serve more as an example.

Set the status code mapping with mehhttp.SetHTTPStatusCodeMapping. You can then log and respond using mehhttp.LogAndRespondError. This logs the error along with request details and responds with the determined HTTP status code and an empty message.

The following additional error codes are provided:

  • mehhttp-communication: Used for all problems regarding client communication because communication is unstable by nature and not always an internal error.
  • mehhttp-service-not-reachable: Used for problems with requesting third-party services.

PostgreSQL support

Documentation

Support for errors of type pgconn.PgError is provided using these generators:

func NewQueryDBErr(err error, message string, query string) error
func NewScanRowsErr(err error, message string, query string) error 

NewQueryDBErr returns a meh.ErrBadInput-error if the error code has prefix 22 (data exception) or 23 (integrity constraint violation).

Documentation

Overview

Package meh adds typed errors, codes and reusable details to Go's native errors.

Index

Constants

View Source
const (
	MapFieldErrorCode    = "x_code"
	MapFieldErrorMessage = "x_err_message"
)

Field names for usage in ToMap.

Variables

This section is empty.

Functions

func ApplyCode

func ApplyCode(err error, code Code) error

ApplyCode wraps the given error with the Code.

func ApplyDetails

func ApplyDetails(err error, details Details) error

ApplyDetails wraps the given error with an ErrNeutral and the given Details.

func ApplyStackTrace

func ApplyStackTrace(err error) error

ApplyStackTrace applies the current stack trace to the given error.

func ClearPassThrough added in v1.7.0

func ClearPassThrough(err error) error

ClearPassThrough returns the given error with all pass-through-fields (from wrapped errors as well).

func Finalize added in v1.7.0

func Finalize(err error) error

Finalize alters the given error for handing it off to other libraries. If the given error is nil, nil is returned. If it contains a pass-through, indicated by Error.WrappedErrPassThrough being true, the wrapped error is returned from the first one, where pass-through is set. Otherwise, the error is returned as with Cast.

func NewBadInputErr

func NewBadInputErr(message string, details Details) error

NewBadInputErr creates a new ErrBadInput with the given message and details.

func NewBadInputErrFromErr

func NewBadInputErrFromErr(err error, message string, details Details) error

NewBadInputErrFromErr creates a new ErrBadInput with the given error to be wrapped, message and details.

func NewErr added in v1.6.0

func NewErr(code Code, message string, details Details) error

NewErr creates a new Error with the given Code, message and details.

func NewErrFromErr added in v1.6.0

func NewErrFromErr(err error, code Code, message string, details Details) error

NewErrFromErr creates a new Error with the given wrapped one, Code, message and details.

func NewForbiddenErr added in v1.5.0

func NewForbiddenErr(message string, details Details) error

NewForbiddenErr creates a new ErrForbidden with the given message and details.

func NewForbiddenErrFromErr added in v1.5.0

func NewForbiddenErrFromErr(err error, message string, details Details) error

NewForbiddenErrFromErr creates a new ErrForbidden with the given error to be wrapped, message and details.

func NewInternalErr

func NewInternalErr(message string, details Details) error

NewInternalErr creates a new ErrInternal with the given message and details.

func NewInternalErrFromErr

func NewInternalErrFromErr(err error, message string, details Details) error

NewInternalErrFromErr creates a new ErrInternal with the given error to be wrapped, message and details.

func NewNotFoundErr

func NewNotFoundErr(message string, details Details) error

NewNotFoundErr creates a new ErrNotFound with the given message and details.

func NewNotFoundErrFromErr

func NewNotFoundErrFromErr(err error, message string, details Details) error

NewNotFoundErrFromErr creates a new ErrNotFound with the given error to be wrapped, message and details.

func NewPassThroughErr added in v1.7.0

func NewPassThroughErr(err error, code Code, message string, details Details) error

NewPassThroughErr is similar to NewErrFromErr with the only difference that Error.WrappedErrPassThrough is set to true. This makes Finalize return the given error, even if wrapped inside *Error.

func NewUnauthorizedErr added in v1.3.0

func NewUnauthorizedErr(message string, details Details) error

NewUnauthorizedErr creates a new ErrUnauthorized with the given message and details.

func NewUnauthorizedErrFromErr added in v1.5.0

func NewUnauthorizedErrFromErr(err error, message string, details Details) error

NewUnauthorizedErrFromErr creates a new ErrUnauthorized with the given error to be wrapped, message and details.

func NilOrWrap added in v1.4.0

func NilOrWrap(err error, message string, details Details) error

NilOrWrap returns nil if the given error is nil or calls meh.Wrap on it otherwise.

func ToMap

func ToMap(err error) map[string]interface{}

ToMap returns the details of the given error as a key-value map with appended enhanced information regarding the error itself (Error.Code to MapFieldErrorCode and the Error.Error-message to MapFieldErrorMessage).

func Wrap

func Wrap(toWrap error, message string, details Details) error

Wrap wraps the given error with an ErrNeutral, the message and details. If no details should be added, set them nil. However, the parameter is mandatory in order to enforce providing as much detail as possible.

Warning: Wrap will NOT add a stack trace unlike errors.Wrap as this seems inconvenient when calling Wrap while bubbling!

Types

type Code

type Code string

Code is the type of error in Error.

const (
	// ErrUnexpected is the default error that is used when no other Code is
	// specified.
	ErrUnexpected Code = ""
	// ErrInternal is used for basic internal errors.
	ErrInternal Code = "internal"
	// ErrBadInput is used when submitted data was invalid. This should be used,
	// when handling external input, e.g. client data or database requests failing
	// because of constraint violation, etc.
	ErrBadInput Code = "bad-input"
	// ErrNotFound is used when requested resources where not found.
	ErrNotFound Code = "not-found"
	// ErrNeutral is used mainly for wrapping in order to not change the Code.
	ErrNeutral Code = "neutral"
	// ErrUnauthorized is used for when the caller is not known but a resource
	// required authorized access.
	ErrUnauthorized Code = "unauthorized"
	// ErrForbidden is used for unauthorized access to resources.
	ErrForbidden Code = "forbidden"
)

func ErrorCode

func ErrorCode(err error) Code

ErrorCode returns the first non ErrNeutral Code for the given error.

type Details

type Details map[string]interface{}

Details are optionally provided error details in Error.Details that are used for easier debugging and error locating.

type Error

type Error struct {
	// Code is the type of Error.
	//
	// Warning: The Code only applies to the current level. For checking the actual
	// level (ignoring ErrNeutral like being added by ApplyCode or Wrap), you need to
	// use ErrorCode!
	Code Code
	// WrappedErr is an optionally wrapped error for example added when wrapping
	// low-level errors or using Wrap.
	WrappedErr error
	// WrappedErrPassThrough describes whether the WrappedErr should be returned on
	// Finalize. This is useful if the wrapped error is relevant for other libraries,
	// etc.
	WrappedErrPassThrough bool
	// Message is an internal error message that is used when generating the error
	// message if not an empty string.
	Message string
	// Details is any optionally added information.
	Details Details
	// Trace is the stack trace to use (set it via ApplyStackTrace).
	Trace StackTrace
}

Error is the container for any relevant error information that needs to be kept when bubbling. For wrapping errors use Wrap. You can create an Error manually or by using generators like NewInternalErrFromErr.

func Cast

func Cast(err error) *Error

Cast tries to Cast the given error to *Error. In case of failure, a new ErrUnexpected is created, wrapping the original error.

func (*Error) Error

func (e *Error) Error() string

Error is used for implementing the error interface and printing the error string by unwrapping errors. The error string will not contain the error code or any further details but only messages.

func (*Error) MarshalJSON added in v1.9.0

func (e *Error) MarshalJSON() ([]byte, error)

MarshalJSON marshals the Error into a JSON representation.

func (*Error) StackTrace

func (e *Error) StackTrace() errors.StackTrace

StackTrace returns the lowest-level errors.StackTrace for the Error if one was set.

func (*Error) UnmarshalJSON added in v1.9.0

func (e *Error) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals an Error from JSON representation.

type ErrorUnwrapper

type ErrorUnwrapper struct {
	// contains filtered or unexported fields
}

ErrorUnwrapper is used for iterating over the recursive structure of wrapped errors in Error.WrappedErr.

func NewErrorUnwrapper

func NewErrorUnwrapper(err error) *ErrorUnwrapper

NewErrorUnwrapper allows iterating the given error from top to bottom level using ErrorUnwrapper.Next in a loop and getting the current level's error using ErrorUnwrapper.Current.

func (*ErrorUnwrapper) Current

func (it *ErrorUnwrapper) Current() error

Current returns the current error. Remember to call Next before the first Current call.

func (*ErrorUnwrapper) Level

func (it *ErrorUnwrapper) Level() int

Level returns the current level. Starting at -1, it will increment from 0 for each Next-call.

func (*ErrorUnwrapper) Next

func (it *ErrorUnwrapper) Next() bool

Next unwraps the current error.

Warning: You always need to make a Next call before being able to retrieve the first error via Current. The reason for this is that it allows simple iterating in a for-loop using Next as condition like when scanning rows using the sql-package.

type StackTrace

type StackTrace struct {
	// StackTrace is the actual errors.StackTrace.
	StackTrace errors.StackTrace
	// StackTraceStr is a formatted stack trace from debug.Stack.
	StackTraceStr string
}

StackTrace holds an errors.StackTrace as well as a formatted stack trace for usage in logging. You can add one using ApplyStackTrace.

Directories

Path Synopsis
Package mehgin provides some wrappers and utils for bridging mehhttp and gin.
Package mehgin provides some wrappers and utils for bridging mehhttp and gin.
Package mehhttp provides functionality for handling and responding errors.
Package mehhttp provides functionality for handling and responding errors.
Package mehlog allows logging of meh.Error to zap.Logger.
Package mehlog allows logging of meh.Error to zap.Logger.
Package mehpg provides error functionality regarding postgres-errors.
Package mehpg provides error functionality regarding postgres-errors.

Jump to

Keyboard shortcuts

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