errors

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 4, 2019 License: MIT Imports: 7 Imported by: 9

README

Errors for GO

This library introduces an advanced error type for golang. It's advantages are better error comparison for error handling and automated test, as well as a built-in distinction between safe error messages to be passed to an end-user (e.g. API client) and technical details that can be written to a log file for further investigation.

Getting started

As this repository is compatible with go mod, it is sufficient to include the following package in your go code:

import "github.com/sbreitf1/errors"

Usage

The main error type of this package is Error which is also compatible with the commonly used error interface. Every instance of Error is typically generated from a globally defined template using Make():

import "github.com/sbreitf1/errors"

var (
    // template definition:
    ArgumentError = errors.New("Argument %s is not valid")
)

func FooBar(positiveValue int) errors.Error {
    if positiveValue < 0 {
        // instantiate error from template:
        return ArgumentError.Make().Args("positiveValue")
    }
    return nil
}

As can be seen in this example, templates can define format strings as consumed by fmt.Sprintf() that are evaluated by a later call to Args() supplying the content. You can also overwrite the whole message using Msg() on the generated error, but this will force the error message to be marked as unsafe.

A key element of this error type is the ability to define the safeness of error messages that specify which information can be displayed to API users without revealing critical secrets and implementation details. Call the Safe() mutator function after changing the error message via Msg() to allow printing the message in public contexts:

SafeArgumentError = errors.New("Argument %s is not valid").Safe()

Errors derived from this template will be safe and can be printed to public contexts. A call to Args() will maintain the safeness state as it only fills expected fields. Changing the message using Msg(), however, will remove the safeness-flag as stated above. A call to SafeString() will return the safe error message. If the error is not safe, a generic error message will be returned, including a unique id referring to this error instance. Printing the full error message Error() including stack trace and id to a log file allows for an indepth view without revealing details to the API client.

Comparison

Another advantage of this error package is the advanced and unified typing system. When creating a new error template using the global function New(ErrorType) you must specify a string denoting the error type of the new error. This error type string is used for comparison regardless of the actual message content. This allows for detailed error messages that can be compared using the same mechanisms as generic errors:

FileNotFoundError = errors.New("File %s not found")

err1 := FileNotFoundError.Make().Args("foo.txt")
err1.Is(FileNotFoundError) // => true, is instance of template FileNotFoundError

err2 := FileNotFoundError.Make().Args("bar.jpg");
err2.Equals(err1) // => true, different error messages but same type "FileNotFoundError"

Alternatively you can use the global functions AreEqual(error,error) and InstanceOf(error,Template) for checking in cases where the values might be nil:

InstanceOf(err1, FileNotFoundError) // => true
AreEqual(err1, err2) // => true
Logging and HTTP Responses

This package offers detailed logging and is compatible with the Gin framework for HTTP request handling. To use both functions in conjunction, you only need one call on a returned error object:

func handleRequest(c *gin.Context) {
    if err := someHandler(); err != nil {
        err.ToRequestAndLog(c)
        return
    }
}

The method ToRequestAndLog(RequestAborter, ...TypedError) executes both ToLog(...TypedError) and ToRequest(RequestAborter). The interface RequestAborter specifies the method AbortWithStatusJSON(int, interface{}) as used by the Gin framework for returning the given HTTP response code and JSON representation of a data object.

You can pass an arbitrary collection of errors and templates to ToLog(...TypedError) to specify which errors should be ignored. You may list functional errors here that should be reported to the API client but are not required in a log file. Furthermore, you can redirect logging by setting errors.Logger to an arbitrary function (string, ...interface{}) to write to a custom logger.

If you carefully maintain the error flags and error propagation in your application code, you won't need any conditions here as ToRequestAndLog will consider all parameters when printing the error message to log and request.

Mutator Functions

Mutator functions like Msg(), Args() and Safe() are used to change a specific property of the error. Every mutator function returns a new copy of Error allowing for a compact syntax. The following mutator functions are available on templates:

Function Effect
Track() Generate id for this error and print message to log (default)
Untrack() Disable automatic print to log. No id and stack trace will be generated for untracked errors
Trace() Allow stack traces for this error. This also marks the error template as tracked
NoTrace() Disallow stack traces for this error (default)
Safe() Set the safeness flag for this error
Msg(string, args...) Set the message for this error. If no args are supplied, the format string will be evaluated after a call to Args(args...)
HTTPCode(int) Sets the HTTP response code for this error
ErrCode(int) Sets the API error code for this error
API(int, int) A shortcut for .HTTPCode(int).ErrCode(int).Safe().Untrack() often used for functional API errors

Most of these methods are also available on errors. See the following list for a complete overview:

Function Effect
Untrack() Remove id and stack trace from this error and disable automatic log printing
NoTrace() Remove stack trace from this error
Safe() Set the safeness flag for this error
Msg(string, args...) Set the message for this error. If no args are supplied, the format string will be evaluated after a call to Args(args...)
Args(args...) Pass the format arguments for a previous call to Msg(string)
Cause(error) Saves a causing error as nested object in this error. Cause error strings will be appended to the error message
StrCause(string, args...) Generates a new generic error with message and appends it as cause
Expand(string, args...) Returns a copy of this error with the given error message and sets itself as cause
ExpandSafe(string, args...) Returns a copy of this error with the given error message with safeness-flag and sets itself as cause
HTTPCode(int) Sets the HTTP response code for this error
ErrCode(int) Sets the API error code for this error
Interopability

Log output of the errors package can be processed by any method that accepts parameters like fmt.Sprintf. If you are using Logrus you can simply use the Errorf function as logger:

errors.Logger = logrus.Errorf

By default all errors are printed directly to StdOut.

Best Practices

Error instantiation

Always use globally defined error templates for error instantiation. You may also define format messages without passing arguments to delay filling in the actual values when they are available in the application context. Use Make() during execution to instantiate a new error and prepare id and stack trace at this location:

var (
    DatabaseError = errors.New("Database unreachable")
    ElementNotFoundError = errors.New("Did not find resource %s").API(404, 0)
)

func example() {
    dbErr := DatabaseError.Make()
    queryErr := ElementNotFoundError.Make().Args("foobar")
}
Error propagation

Use Cause(error) to encapsulate a typical error object in the error model of this package:

var (
    ReadFileError = errors.New("Unable to read file %q")
)

function readData(file string) (string, errors.Error) {
    data, err := ioutil.ReadFile(file)
    if err != nil {
        return "", ReadFileError.Make().Args(file).Cause(err);
    }
    return string(data), nil
}

Then use Expand(string, args...) to propagate errors while maintaining the original error type:

function readResourceFile(relativePath string) (string, errors.Error) {
    data, err := readData(filepath.Join("data/resources", relativePath))
    if err != nil {
        return "", err.Expand("Could not read resource file")
    }
    return data, nil
}

Alternatively, you can use Cause(error) to propagate errors to semantically distinct steps:

var (
    ReadKeyError = errors.New("Unable to parse key")
)

function parseKey(file string) (*Key, errors.Error) {
    data, err := ioutil.ReadFile(file)
    if err != nil {
        return nil, ReadKeyError.Make().Cause(err)
    }
    [...]
}

For a fast way of propagating classical errors, you can use errors.Wrap that generates a new traced error using the input error type name:

function deleteFile(file string) (errors.Error) {
    return errors.Wrap(os.Remove(file))
}

As you can see, no special handling for <nil> is required here, as errors.Wrap will also return <nil> in this case.

TL;DR

TODO: short overview

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// PrintUnsafeErrors controls wether unsafe (technical) error messages should be visible to the user in response messages.
	PrintUnsafeErrors = false

	// Logger is called to print errors and stack traces to log.
	Logger = DefaultStdOutLogger
)
View Source
var (
	// GenericError represents a generic error with stack trace.
	GenericError = New("An error occured").Trace()
	// ConfigurationError is an error that is caused by an invalid configuration.
	ConfigurationError = New("The specified configuration is not valid")
	// ArgumentError denotes a missing or invalid argument.
	ArgumentError = New("An invalid argument has been supplied")
)
View Source
var (
	// GenericSafeErrorMessage denotes the message replacement when exposing unsafe errors via API.
	GenericSafeErrorMessage string
)

Functions

func AreEqual

func AreEqual(err1, err2 error) bool

AreEqual returns true if the type of both errors is the same regardless of the specific error message. Also returns true if both errors are nil.

func Assert

func Assert(t *testing.T, expected interface{}, actual error, msgAndArgs ...interface{}) bool

Assert performs test assertions to ensure error equality. The expected error can be of type error or errors.Template.

func AssertNil

func AssertNil(t *testing.T, actual error, msgAndArgs ...interface{}) bool

AssertNil performs test assertions to ensure the given error is nil.

func DefaultStdOutLogger

func DefaultStdOutLogger(msg string, args ...interface{})

DefaultStdOutLogger prints all error messages to StdOut.

func InstanceOf

func InstanceOf(err error, template Template) bool

InstanceOf returns true if the given error is an instance of the given template. A nil error always returns false.

func ToRequest

func ToRequest(r RequestAborter, err error) bool

ToRequest writes the given error to a HTTP request and returns true if err was not nil.

Types

type APIError

type APIError struct {
	ResponseCode int    `json:"-"`
	ErrorCode    int    `json:"code"`
	Message      string `json:"message"`
}

APIError represents a generic error repsonse object with code and message.

func API

func API(httpCode, errCode int, message string) APIError

API returns a new APIError object.

func DefaultAPI

func DefaultAPI(message string) APIError

DefaultAPI returns a new APIError object using the default http and error codes.

func (APIError) ToRequest

func (err APIError) ToRequest(r RequestAborter)

ToRequest writes this APIError object to a HTTP request and aborts pipeline execution.

type Error

type Error interface {
	error
	TypedError
	fmt.Stringer

	SafeString() string

	GetID() string
	GetStackTrace() string

	// Untrack disables id and stack trace printing for this error.
	Untrack() Error
	// NoTrace disables stack trace printing.
	NoTrace() Error
	// Msg returns a new Error object and replaces the error message. You can supply all formatting args later using Args() to skip formatting in this call.
	Msg(msg string, args ...interface{}) Error
	// Args returns a new Error object with filled placeholders. A safe message remains safe.
	Args(args ...interface{}) Error
	// Cause adds the given error as cause. It's error message will be appended to the output.
	Cause(err error) Error
	// StrCause adds a detailed error message as cause.
	StrCause(str string, args ...interface{}) Error
	// Expand creates a copy of this error with given message and sets the current error as cause.
	Expand(msg string, args ...interface{}) Error
	// ExpandSafe creates a copy of this error with given message and sets the current error as cause. The expanded message is marked as safe.
	ExpandSafe(msg string, args ...interface{}) Error

	// Tag adds a named tag to the error.
	Tag(tag string) Error
	// IsTagged returns whether the error contains a named tag. Tags with attached value are not captured by this method.
	IsTagged(tag string) bool
	// TagStr adds a named tag with string value to the error.
	TagStr(tag, value string) Error
	// GetTagStr returns a string tag or false, if no tag is set.
	GetTagStr(tag string) (string, bool)
	// TagInt adds a named tag with integer value to the error.
	TagInt(tag string, value int) Error
	// GetTagInt returns an integer tag or false, if no tag is set.
	GetTagInt(tag string) (int, bool)

	// Equals returns true when the error types are equal (ignoring the explicit error message).
	Equals(other error) bool
	// Is returns trhe when the error is an instance of the given template.
	Is(template Template) bool

	// HTTPCode sets the http response code.
	HTTPCode(code int) Error
	// ErrCode sets the api error code.
	ErrCode(code int) Error
	// Safe marks the error as safe for printing to end-user.
	Safe() Error
	// API returns the corresponding APIError object.
	API() APIError
	// ToRequest writes the APIError message representation to a HTTP request and aborts pipeline execution.
	ToRequest(r RequestAborter)
	// ToRequestAndLog calls ToRequest(r) and ToLog(...except).
	ToRequestAndLog(r RequestAborter, except ...TypedError)
	// ToRequestAndLog calls ToRequest(r) and ForceLog(...except).
	ToRequestAndForceLog(r RequestAborter, except ...TypedError)

	// ToLog writes the error message with debug data to the log.
	ToLog(except ...TypedError)
	// ForceLog writes the error message (and also untracked ones) with debug data to the log.
	ForceLog(except ...TypedError)
}

Error is used as base error type in the whole application. Use Wrap(error) to encapsulate errors from third-party code.

func Wrap

func Wrap(baseErr error) Error

Wrap encapsulates any go-error in the extended Error type. Returns nil if baseErr is nil.

func WrapT

func WrapT(baseErr error) Error

WrapT encapsulates any go-error in the extended Error type and appends the base error type to the message. Returns nil if baseErr is nil.

type ErrorType

type ErrorType string

ErrorType represents the base type of an error regardless of the specific error message.

func (ErrorType) String

func (e ErrorType) String() string

type RequestAborter

type RequestAborter interface {
	AbortWithStatusJSON(int, interface{})
}

RequestAborter defines the required functionality to abort an HTTP request and is compatible with *gin.Context.

type Template

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

Template represents an error template that can be instatiated to an error using Make().

func New

func New(msg string, args ...interface{}) Template

New returns an error template and uses the message format string as error type.

func (Template) API

func (t Template) API(httpCode, errCode int) Template

API untracks the error, marks it as safe and update the error and response codes.

func (Template) Args

func (t Template) Args(args ...interface{}) Template

Args fills the message placeholders with the given arguments.

func (Template) ErrCode

func (t Template) ErrCode(code int) Template

ErrCode sets the api error code.

func (Template) GetType

func (t Template) GetType() ErrorType

GetType returns the underlying error type of this template.

func (Template) HTTPCode

func (t Template) HTTPCode(code int) Template

HTTPCode sets the http response code.

func (Template) Make

func (t Template) Make() Error

Make instatiates an error using this template. A call to this method generates a new ID and StackTrace from the calling location if tracked and traced.

func (Template) MakeTraced

func (t Template) MakeTraced(depth int) Error

MakeTraced instatiates an error using this template. A call to this method tracks and traces the error and generates a new ID and StackTrace from the calling location. Use the depth parameter to skip a certain number of stack frames in the trace.

func (Template) Msg

func (t Template) Msg(msg string, args ...interface{}) Template

Msg replaces the error message. You can supply all formatting args later using Args() to skip formatting in this call.

func (Template) NoTrace

func (t Template) NoTrace() Template

NoTrace disables stack trace printing.

func (Template) Safe

func (t Template) Safe() Template

Safe marks the error as safe for printing to end-user.

func (Template) Tag added in v1.1.0

func (t Template) Tag(tag string) Template

Tag adds a named tag to the template.

func (Template) TagInt added in v1.1.0

func (t Template) TagInt(tag string, value int) Template

TagInt adds a named tag with integer value to the template.

func (Template) TagStr added in v1.1.0

func (t Template) TagStr(tag, value string) Template

TagStr adds a named tag with string value to the template.

func (Template) Trace

func (t Template) Trace() Template

Trace enables stack trace printing.

func (Template) Track

func (t Template) Track() Template

Track enables id printing for this error.

func (Template) Untrack

func (t Template) Untrack() Template

Untrack disabled id and stack trace printing for this error.

type TypedError

type TypedError interface {
	// GetType returns the type of the error that is used for comparison.
	GetType() ErrorType
}

TypedError represents errors and templates that define an error type.

Jump to

Keyboard shortcuts

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