errs

package module
v1.3.4 Latest Latest
Warning

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

Go to latest
Published: May 9, 2026 License: Apache-2.0 Imports: 9 Imported by: 136

README

errs -- Error handling for Golang

ci status codeql status GitHub license GitHub release

Package errs implements functions to manipulate error instances. This package is required Go 1.20 or later.

Migrated repository to github.com/goark/errs

Design goals

  • Wrap any error and collect context at the point of failure
  • Add arbitrary key/value context with WithContext
  • Include caller function name in context by default
  • Print structured error data with %+v (JSON-like output)
  • Handle multi-errors in a concurrency-safe way via errs.Errors

Development

Requirements
  • Go 1.20 or later
  • Task command
Local validation
task test
task govulncheck

Run all maintenance tasks:

task

CI Workflows

  • ci: lint (golangci-lint with gosec), tests, and govulncheck
  • CodeQL: scheduled and push/PR static analysis

Usage

Sample programs

All sample files under sample/ use the run build tag.

go run -tags run ./sample/sample1.go
Print formats
  • %v: human readable error message
  • %#v: Go-syntax-like internal structure
  • %+v: structured JSON-like representation
Helper functions compatible with stdlib

errs.Is, errs.As, and errs.Unwrap are thin wrappers around errors.Is, errors.As, and errors.Unwrap.

errs.Unwraps returns []error and works for both single-cause and multi-cause errors. For a single cause, it returns a one-element slice. For multiple causes, it returns all causes as a slice.

errs.EncodeJSON serializes generic error values by traversing unwrap chains when possible.

Edge-case behavior
  • errs.New("") returns nil
  • errs.Wrap(nil) returns nil
  • If WithCause is given multiple times, the last cause is used
  • errs.Join(...) ignores nil arguments and returns nil if all arguments are nil
Concurrency notes
  • errs.Errors is goroutine-safe for container operations such as Add, ErrorOrNil, and Unwrap.
  • Errors stored in errs.Errors are not guaranteed to be goroutine-safe.
  • errs.Error has mutable state (Context map), so avoid concurrent mutation while formatting or encoding the same instance.
Create new error instance with cause
package main

import (
    "fmt"
    "os"

    "github.com/goark/errs"
)

func checkFileOpen(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return errs.New(
            "file open error",
            errs.WithCause(err),
            errs.WithContext("path", path),
        )
    }
    defer file.Close()

    return nil
}

func main() {
    if err := checkFileOpen("not-exist.txt"); err != nil {
        fmt.Printf("%v\n", err)  // file open error: open not-exist.txt: no such file or directory
        fmt.Printf("%#v\n", err) // *errs.Error{Err:&errors.errorString{s:"file open error"}, Cause:&fs.PathError{Op:"open", Path:"not-exist.txt", Err:0x2}, Context:map[string]interface {}{"function":"main.checkFileOpen", "path":"not-exist.txt"}}
        fmt.Printf("%+v\n", err) // {"Type":"*errs.Error","Err":{"Type":"*errors.errorString","Msg":"file open error"},"Context":{"function":"main.checkFileOpen","path":"not-exist.txt"},"Cause":{"Type":"*fs.PathError","Msg":"open not-exist.txt: no such file or directory","Cause":{"Type":"syscall.Errno","Msg":"no such file or directory"}}}
    }
}
Wrapping error instance
package main

import (
    "fmt"
    "os"

    "github.com/goark/errs"
)

func checkFileOpen(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return errs.Wrap(
            err,
            errs.WithContext("path", path),
        )
    }
    defer file.Close()

    return nil
}

func main() {
    if err := checkFileOpen("not-exist.txt"); err != nil {
        fmt.Printf("%v\n", err)  // open not-exist.txt: no such file or directory
        fmt.Printf("%#v\n", err) // *errs.Error{Err:&fs.PathError{Op:"open", Path:"not-exist.txt", Err:0x2}, Cause:<nil>, Context:map[string]interface {}{"function":"main.checkFileOpen", "path":"not-exist.txt"}}
        fmt.Printf("%+v\n", err) // {"Type":"*errs.Error","Err":{"Type":"*fs.PathError","Msg":"open not-exist.txt: no such file or directory","Cause":{"Type":"syscall.Errno","Msg":"no such file or directory"}},"Context":{"function":"main.checkFileOpen","path":"not-exist.txt"}}
    }
}
Wrapping error instance with cause
package main

import (
    "errors"
    "fmt"
    "os"

    "github.com/goark/errs"
)

func checkFileOpen(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return errs.Wrap(
            errors.New("file open error"),
            errs.WithCause(err),
            errs.WithContext("path", path),
        )
    }
    defer file.Close()

    return nil
}

func main() {
    if err := checkFileOpen("not-exist.txt"); err != nil {
        fmt.Printf("%v\n", err)  // file open error: open not-exist.txt: no such file or directory
        fmt.Printf("%#v\n", err) // *errs.Error{Err:&errors.errorString{s:"file open error"}, Cause:&fs.PathError{Op:"open", Path:"not-exist.txt", Err:0x2}, Context:map[string]interface {}{"function":"main.checkFileOpen", "path":"not-exist.txt"}}
        fmt.Printf("%+v\n", err) // {"Type":"*errs.Error","Err":{"Type":"*errors.errorString","Msg":"file open error"},"Context":{"function":"main.checkFileOpen","path":"not-exist.txt"},"Cause":{"Type":"*fs.PathError","Msg":"open not-exist.txt: no such file or directory","Cause":{"Type":"syscall.Errno","Msg":"no such file or directory"}}}
    }
}
Create new error instance with multiple causes
package main

import (
    "errors"
    "fmt"
    "io"
    "os"

    "github.com/goark/errs"
)

func generateMultiError() error {
    return errs.New("error with multiple causes", errs.WithCause(errors.Join(os.ErrInvalid, io.EOF)))
}

func main() {
    err := generateMultiError()
    fmt.Printf("%+v\n", err)            // {"Type":"*errs.Error","Err":{"Type":"*errors.errorString","Msg":"error with multiple causes"},"Context":{"function":"main.generateMultiError"},"Cause":{"Type":"*errors.joinError","Msg":"invalid argument\nEOF","Cause":[{"Type":"*errors.errorString","Msg":"invalid argument"},{"Type":"*errors.errorString","Msg":"EOF"}]}}
    fmt.Println(errors.Is(err, io.EOF)) // true
}
Handling multiple errors
package main

import (
    "errors"
    "fmt"
    "io"
    "os"

    "github.com/goark/errs"
)

func generateMultiError() error {
    return errs.Join(os.ErrInvalid, io.EOF)
}

func main() {
    err := generateMultiError()
    fmt.Printf("%+v\n", err)            // {"Type":"*errs.Errors","Errs":[{"Type":"*errors.errorString","Msg":"invalid argument"},{"Type":"*errors.errorString","Msg":"EOF"}]}
    fmt.Println(errors.Is(err, io.EOF)) // true
}
package main

import (
    "fmt"
    "sync"

    "github.com/goark/errs"
)

func generateMultiError() error {
    errlist := &errs.Errors{}
    var wg sync.WaitGroup
    for i := 1; i <= 2; i++ {
        i := i
        wg.Add(1)
        go func() {
            defer wg.Done()
            errlist.Add(fmt.Errorf("error %d", i))
        }()
    }
    wg.Wait()
    return errlist.ErrorOrNil()
}

func main() {
    err := generateMultiError()
    fmt.Printf("%+v\n", err) // {"Type":"*errs.Errors","Errs":[{"Type":"*errors.errorString","Msg":"error 2"},{"Type":"*errors.errorString","Msg":"error 1"}]}
}
Structured logging with Zap

Use the submodule github.com/goark/errs/zapobject to log errors as structured objects.

logger.Error("failed", zap.Object("error", zapobject.New(err)))

Without zapobject, zap.Error(err) writes string fields only.

Background article (Japanese)

Documentation

Overview

Package errs implements functions to manipulate error instances.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func As

func As(err error, target interface{}) bool

As is conpatible with errors.As.

func Cause deprecated

func Cause(err error) error

Cause function finds cause error in target error instance.

Deprecated: should not be used

func EncodeJSON

func EncodeJSON(err error) string

EncodeJSON function dumps out error instance with JSON format.

Example
package main

import (
	"fmt"
	"os"

	"github.com/goark/errs"
)

func main() {
	_, err := os.Open("not-exist.txt")
	fmt.Printf("%v", errs.EncodeJSON(err))
}
Output:
{"Type":"*fs.PathError","Msg":"open not-exist.txt: no such file or directory","Cause":{"Type":"syscall.Errno","Msg":"no such file or directory"}}

func Is

func Is(err, target error) bool

Is is conpatible with errors.Is.

func Join added in v1.3.0

func Join(errlist ...error) error

Join function returns Errors instance.

Join ignores nil elements in arguments and returns nil if all elements are nil.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/goark/errs"
)

func main() {
	err := errs.Join(errors.New("error 1"), errors.New("error 2"))
	fmt.Println(err)
	errlist, ok := err.(*errs.Errors)
	if !ok {
		return
	}
	errlist.Add(errors.New("error 3"))
	fmt.Printf("%+v\n", errlist.ErrorOrNil())
}
Output:
error 1
error 2
{"Type":"*errs.Errors","Errs":[{"Type":"*errors.errorString","Msg":"error 1"},{"Type":"*errors.errorString","Msg":"error 2"},{"Type":"*errors.errorString","Msg":"error 3"}]}

func New

func New(msg string, opts ...ErrorContextFunc) error

New function returns an error instance with message and context informations.

Example
package main

import (
	"fmt"
	"os"

	"github.com/goark/errs"
)

func main() {
	err := errs.New(
		"wrapper error",
		errs.WithCause(os.ErrInvalid),
		errs.WithContext("foo", "bar"),
	)
	fmt.Printf("%+v", err)
}
Output:
{"Type":"*errs.Error","Err":{"Type":"*errors.errorString","Msg":"wrapper error"},"Context":{"foo":"bar","function":"github.com/goark/errs_test.ExampleNew"},"Cause":{"Type":"*errors.errorString","Msg":"invalid argument"}}

func Unwrap

func Unwrap(err error) error

Unwrap is conpatible with errors.Unwrap.

func Unwraps added in v1.2.0

func Unwraps(err error) []error

Unwraps function finds cause errors ([]error slice) in target error instance.

func Wrap

func Wrap(err error, opts ...ErrorContextFunc) error

Wrap function returns a wrapping error instance with context informations.

Types

type Error

type Error struct {
	Err     error
	Cause   error
	Context map[string]interface{}
	// contains filtered or unexported fields
}

Error type is a implementation of error interface. This type is for wrapping cause error instance.

Error is not goroutine-safe. Its Context field is a mutable map and can be modified by SetContext or direct field access.

Example
package main

import (
	"fmt"
	"os"

	"github.com/goark/errs"
)

func main() {
	err := errs.Wrap(
		os.ErrInvalid,
		errs.WithContext("foo1", "bar1"),
	)
	_ = err.(*errs.Error).SetContext("foo2", "bar2")
	fmt.Printf("%+v", err)
}
Output:
{"Type":"*errs.Error","Err":{"Type":"*errors.errorString","Msg":"invalid argument"},"Context":{"foo1":"bar1","foo2":"bar2","function":"github.com/goark/errs_test.ExampleError"}}

func (*Error) EncodeJSON

func (e *Error) EncodeJSON() string

EncodeJSON method returns serialize string of Error with JSON format.

func (*Error) Error

func (e *Error) Error() string

Error method returns error message. This method is a implementation of error interface.

func (*Error) Format

func (e *Error) Format(s fmt.State, verb rune)

Format method returns formatted string of Error instance. This method is a implementation of fmt.Formatter interface.

func (*Error) GoString

func (e *Error) GoString() string

GoString method returns serialize string of Error. This method is a implementation of fmt.GoStringer interface.

func (*Error) Is

func (e *Error) Is(target error) bool

Is method reports whether any error in error's chain matches cause of target error. This method is used in errors.Is function.

func (*Error) MarshalJSON

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

MarshalJSON method returns serialize string of Error with JSON format. This method is implementation of json.Marshaler interface.

func (*Error) SetCause

func (e *Error) SetCause(err error) *Error

SetCause method sets cause error instance

func (*Error) SetContext

func (e *Error) SetContext(name string, value interface{}) *Error

SetContext method sets context information

func (*Error) String

func (e *Error) String() string

String method returns error message. This method is a implementation of fmt.Stringer interface.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap method returns cause error in Error instance. This method is used in errors.Unwrap function.

type ErrorContextFunc

type ErrorContextFunc func(*Error)

ErrorContextFunc type is self-referential function type for New and Wrap functions. (functional options pattern)

func WithCause

func WithCause(err error) ErrorContextFunc

WithCause function returns ErrorContextFunc function value. This function is used in New and Wrap functions that represents context (key/value) data.

func WithContext

func WithContext(name string, value interface{}) ErrorContextFunc

WithContext function returns ErrorContextFunc function value. This function is used in New and Wrap functions that represents context (key/value) data.

type Errors added in v1.3.0

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

Errors is multiple error instance.

Errors protects concurrent access to the container itself, but each contained error may still be non-thread-safe.

Example
package main

import (
	"fmt"
	"sync"

	"github.com/goark/errs"
)

func main() {
	errlist := &errs.Errors{}
	var wg sync.WaitGroup
	for i := 1; i <= 100000; i++ {
		i := i
		wg.Add(1)
		go func() {
			defer wg.Done()
			errlist.Add(fmt.Errorf("error %d", i))
		}()
	}
	wg.Wait()
	fmt.Println("error count =", len(errlist.Unwrap()))
}
Output:
error count = 100000

func (*Errors) Add added in v1.3.0

func (es *Errors) Add(errlist ...error)

Add method adds errors to Errors.

func (*Errors) EncodeJSON added in v1.3.0

func (es *Errors) EncodeJSON() string

EncodeJSON method returns serialize string of Errors with JSON format.

func (*Errors) Error added in v1.3.0

func (es *Errors) Error() string

Error method returns error message. This method is a implementation of error interface.

func (*Errors) ErrorOrNil added in v1.3.0

func (es *Errors) ErrorOrNil() error

ErrorOrNil method returns this as a error type.

func (*Errors) Format added in v1.3.0

func (es *Errors) Format(s fmt.State, verb rune)

Format method returns formatted string of Errors instance. This method is a implementation of fmt.Formatter interface.

func (*Errors) GoString added in v1.3.0

func (es *Errors) GoString() string

GoString method returns serialize string of Errors. This method is a implementation of fmt.GoStringer interface.

func (*Errors) MarshalJSON added in v1.3.0

func (es *Errors) MarshalJSON() ([]byte, error)

MarshalJSON method returns serialize string of Errors with JSON format. This method is implementation of json.Marshaler interface.

func (*Errors) String added in v1.3.0

func (es *Errors) String() string

String method returns error message. This method is a implementation of fmt.Stringer interface.

func (*Errors) Unwrap added in v1.3.0

func (es *Errors) Unwrap() []error

Unwrap method returns error list in Errors instance. This method is used in errors.Unwrap function.

Directories

Path Synopsis
zapobject module

Jump to

Keyboard shortcuts

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