zrr

package module
v0.15.0 Latest Latest
Warning

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

Go to latest
Published: Jan 21, 2022 License: BSD-2-Clause Imports: 4 Imported by: 0

README

Errors with context

Go Report Card GoDoc

The package zrr provides a way to add and inspect, in type safe manner, context for errors.

Most importantly errors.Is, errors.As and errors.Unwrap work with zrr.Error as expected.

Installation

go get github.com/rzajac/zrr

When is it useful?

Imagine somewhere deep in your call tree one of the methods returns an error which bubbles up to the top where it can be logged using for example zerolog. Wouldn't it be great if you could log not only the error message, but its context? Especially that some context information might not be available at the logging level.

This is where zrr might be useful.

Package level errors.

By definition package level errors must be immutable. With zrr you can create package level errors with Imm constructor function.

var ErrPackageLevel = zrr.Imm("package level error", "ECode")

Errors created this way will be immutable, but you still can add context to them. When context adding methods are called on immutable error it's first cloned, and the keys are added on the cloned instance. The cool thing is that errors.Is still works as expected on the cloned instance:

// Create immutable error.
var ErrPackageLevel = zrr.Imm("package level error", "ECode")

// Somewhere in the code use ErrPackageLevel and add context to it.
err := ErrPackageLevel.Str("path", "/path/to/file").Str("code", "ENewCode")

// Notice the error code has not been changed.
fmt.Println(ErrPackageLevel) 
fmt.Println(err)
fmt.Println(errors.Is(err, ErrPackageLevel))
fmt.Println(zrr.GetCode(err))
fmt.Println(zrr.GetStr(err, "path"))

// Output:
// package level error"
// package level error"
// true
// ENewCode true
// /path/to/file true

Wrapping other errors

You can easily decorate other error instances with context fields and keep ability to unwrap them with errors.As(e1, err) and test them with errors.Is(e1, err).

err := errors.New("some error")

e1 := zrr.Wrap(err).Str("key", "value")

fmt.Println(e1)
fmt.Println(errors.Is(e1, err))
fmt.Println(zrr.GetStr(e1, "key"))

// Output:
// some error
// true
// value true

Wrapping zrr errors.

err := zrr.New("my error")

e1 := fmt.Errorf("zrr wrapped: %w", err)

fmt.Println(e1)
fmt.Println(errors.Is(e1, err))

// Output:
// zrr wrapped: my error
// true

Key value example

// Create an error and add bunch of context fields to it.
err := zrr.Wrap(errors.New("std error")).
    Code("ECode").
    Str("str", "string").
    Int("int", 5).
    Float64("float64", 1.23).
    Time("time", time.Date(2020, time.October, 7, 23, 47, 0, 0, time.UTC)).
    Bool("bool", true)

// Somewhere else (maybe during logging extract the context fields.
iter := err.Fields()
for iter.Next() {
    key, val := iter.Get()
    fmt.Printf("%s = %v\n", key, val)
}

// Output:
// bool = true
// code = ECode
// float64 = 1.23
// int = 5
// str = string
// time = 2020-10-07 23:47:00 +0000 UTC

For more examples visit pkg.go.dev.

Inspecting error metadata

var err error
err = zrr.New("message", "ECode").Int("retry", 5)

fmt.Println(zrr.GetInt(err, "retry"))   // 5 true
fmt.Println(zrr.GetInt(err, "not_set")) // 0 false
fmt.Println(zrr.HasKey(err, "retry"))   // true
fmt.Println(zrr.HasKey(err, "not_set")) // false

// Output:
// 5 true
// 0 false
// true
// false

License

BSD-2-Clause

Documentation

Overview

Package zrr provides a way to add and inspect type safe error context.

The error context might be useful for example when logging errors which were created in some deeper parts of your code.

Index

Examples

Constants

View Source
const ECInvJSON = "ECInvJSON"

ECInvJSON represents invalid JSON error code.

Variables

View Source
var ErrInvJSON = Imm("invalid JSON", ECInvJSON)

ErrInvJSON represents package level error indicating JSON structure or format error.

Functions

func GetBool added in v0.3.0

func GetBool(err error, key string) (bool, bool)

GetBool returns the key as a boolean if err is an instance of Error and key exists. If key does not exist, or it is not a boolean it will return false as the second return value.

func GetCode added in v0.5.0

func GetCode(err error) string

GetCode returns error code if error err is instance of Error. If error code is not set it will return empty string.

Example
package main

import (
	"fmt"

	"github.com/rzajac/zrr"
)

func main() {
	err := zrr.New("message", "ECode").Int("retry", 5)

	fmt.Println(zrr.GetCode(err))

}
Output:

ECode

func GetFloat64 added in v0.3.0

func GetFloat64(err error, key string) (float64, bool)

GetFloat64 returns the key as a float64 if err is an instance of Error and key exists. If key does not exist, or it's not a float64 it will return false as the second return value.

func GetInt added in v0.3.0

func GetInt(err error, key string) (int, bool)

GetInt returns the key as an integer if err is an instance of Error and key exists. If key does not exist, or it's not an integer it will return false as the second return value.

Example
package main

import (
	"fmt"

	"github.com/rzajac/zrr"
)

func main() {
	var err error
	err = zrr.New("message", "ECode").Int("retry", 5)

	fmt.Println(zrr.GetInt(err, "retry"))   // 5 true
	fmt.Println(zrr.GetInt(err, "not_set")) // 0 false
	fmt.Println(zrr.HasKey(err, "retry"))   // true
	fmt.Println(zrr.HasKey(err, "not_set")) // false

}
Output:

5 true
0 false
true
false

func GetInt64 added in v0.5.0

func GetInt64(err error, key string) (int64, bool)

GetInt64 returns the key as an int64 if err is an instance of Error and key exists. If key does not exist, or it's not an int64 it will return false as the second return value.

func GetStr added in v0.3.0

func GetStr(err error, key string) (string, bool)

GetStr returns the key as a string if err is an instance of Error and key exists. If key does not exist, or it's not a string it will return false as the second return value.

func GetTime added in v0.3.0

func GetTime(err error, key string) (time.Time, bool)

GetTime returns the key as a time.Time if err is an instance of Error and key exists. If key does not exist, or it's not a time.Time it will return false as the second return value.

func HasCode added in v0.3.0

func HasCode(err error, codes ...string) bool

HasCode returns true if error err is instance of Error and has any of the codes.

func HasKey added in v0.3.0

func HasKey(err error, key string) bool

HasKey returns true if error err is instance of Error and has the key set.

Example
package main

import (
	"fmt"

	"github.com/rzajac/zrr"
)

func main() {
	err := zrr.New("message", "ECode").Int("retry", 5)

	fmt.Println(zrr.GetInt(err, "retry"))
	fmt.Println(zrr.GetInt(err, "not_set"))

}
Output:

5 true
0 false

func IsImmutable added in v0.3.0

func IsImmutable(err error) bool

IsImmutable returns true if error err is instance of Error and is immutable.

Types

type Error

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

Error represents an error with metadata key value pairs.

Example
package main

import (
	"errors"
	"fmt"
	"time"

	"github.com/rzajac/zrr"
)

func main() {
	// Create an error and add a bunch of context fields to it.
	err := zrr.Wrap(errors.New("std error"), "ECode").
		Str("str", "string").
		Int("int", 5).
		Float64("float64", 1.23).
		Time("time", time.Date(2020, time.October, 7, 23, 47, 0, 0, time.UTC)).
		Bool("bool", true)

	fmt.Println(err.Error())
	fmt.Println(zrr.GetStr(err, "str"))
	fmt.Println(zrr.GetInt(err, "int"))
	fmt.Println(zrr.GetFloat64(err, "float64"))
	fmt.Println(zrr.GetTime(err, "time"))
	fmt.Println(zrr.GetBool(err, "bool"))
	fmt.Println(err.ErrCode())

}
Output:

std error
string true
5 true
1.23 true
2020-10-07 23:47:00 +0000 UTC true
true true
ECode
Example (WrappingZrrError)
package main

import (
	"errors"
	"fmt"

	"github.com/rzajac/zrr"
)

func main() {
	err := zrr.New("my error").Str("key", "value")

	e1 := fmt.Errorf("zrr wrapped: %w", err)

	fmt.Println(e1)
	fmt.Println(errors.Is(e1, err))

}
Output:

zrr wrapped: my error
true

func Imm

func Imm(msg string, code ...string) *Error

Imm is a constructor returning new immutable Error instance.

Immutable error instances are never changed when adding / changing fields. They are good choice for package level errors.

Error code is optional, if more than one code is provided the first one will be used.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/rzajac/zrr"
)

func main() {
	// Create immutable error.
	var ErrPackageLevel = zrr.Imm("package level error", "ECode")

	// Somewhere in the code use ErrPackageLevel and add context to it.
	err := zrr.Wrap(ErrPackageLevel, "ENewCode").Str("path", "/path/to/file")

	fmt.Println(ErrPackageLevel, zrr.GetCode(ErrPackageLevel)) // Notice the error code has not been changed.
	fmt.Println(err, zrr.GetCode(err))
	fmt.Println(errors.Is(err, ErrPackageLevel))

}
Output:

package level error ECode
package level error ENewCode
true

func New

func New(msg string, code ...string) *Error

New is a constructor returning new Error instance.

func Newf

func Newf(msg string, args ...interface{}) *Error

Newf is a constructor returning new Error instance. Arguments are handled in the same manner as in fmt.Errorf.

func Wrap

func Wrap(err error, code ...string) *Error

Wrap wraps err in Error instance. It returns nil if err is nil.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/rzajac/zrr"
)

func main() {
	err := errors.New("some error")

	e1 := zrr.Wrap(err).Str("key", "value")

	fmt.Println(e1)
	fmt.Println(errors.Is(e1, err))

}
Output:

some error
true

func (*Error) Bool

func (e *Error) Bool(key string, b bool) *Error

Bool adds the key with val as a boolean to the error.

func (*Error) ErrCode added in v0.14.0

func (e *Error) ErrCode() string

ErrCode returns error code.

func (*Error) Error

func (e *Error) Error() string

Error implements error interface and returns error message and key value pairs associated with it separated by MsgSep.

Example
package main

import (
	"fmt"

	"github.com/rzajac/zrr"
)

func main() {
	err := zrr.New("message").Int("retry", 5)

	fmt.Println(err.Error())

}
Output:

message

func (*Error) Float64

func (e *Error) Float64(key string, f float64) *Error

Float64 adds the key with float64 val to the error.

func (*Error) GetMetadata added in v0.15.0

func (e *Error) GetMetadata() map[string]interface{}

GetMetadata returns error metadata. The returned metadata map should be considered read-only.

func (*Error) Int

func (e *Error) Int(key string, i int) *Error

Int adds the key with integer val to the error.

func (*Error) Int64 added in v0.5.0

func (e *Error) Int64(key string, i int64) *Error

Int64 adds the key with int64 val to the error.

func (*Error) MarshalJSON added in v0.14.0

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

func (*Error) SetErrMetadata added in v0.15.0

func (e *Error) SetErrMetadata(src map[string]interface{}) *Error

SetErrMetadata sets error metadata. The returned instance might be different from the one this method is called if the error is immutable.

func (*Error) SetMetadataFrom added in v0.15.0

func (e *Error) SetMetadataFrom(src MetadataGetter) *Error

SetMetadataFrom is a convenience method setting metadata from an instance which implements MetadataGetter.

func (*Error) Str

func (e *Error) Str(key string, s string) *Error

Str adds the key with string val to the error.

func (*Error) StrAppend added in v0.10.0

func (e *Error) StrAppend(key string, s string) *Error

StrAppend appends the string s (prefixed with semicolon) to the string represented by key k. The key will be added if it doesn't exist. If the key already exists and is not a string the old key will be overwritten.

func (*Error) Time

func (e *Error) Time(key string, t time.Time) *Error

Time adds the key with val as a time to the error.

func (*Error) UnmarshalJSON added in v0.14.0

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

UnmarshalJSON unmarshal error's JSON representation. Notes:

  • all metadata numeric values will be unmarshalled as float64

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap unwraps original error.

type MetadataErrSetter added in v0.15.0

type MetadataErrSetter interface {
	// SetErrMetadata sets error metadata. The returned instance might be
	// different from the one this method is called if the error is immutable.
	SetErrMetadata(map[string]interface{}) *Error
}

MetadataErrSetter is an interface wrapping SetErrMetadata method.

type MetadataGetter added in v0.15.0

type MetadataGetter interface {
	// GetMetadata returns error metadata. The returned metadata map should be
	// considered read-only.
	GetMetadata() map[string]interface{}
}

MetadataGetter is an interface wrapping GetMetadata method.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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