failure

package module
v2.0.0-...-2551069 Latest Latest
Warning

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

Go to latest
Published: Apr 19, 2024 License: MIT Imports: 8 Imported by: 38

README

failure

Go Reference

Package failure is an error handling library for Go. It allows you to create, wrap, and handle errors with additional context and features.

Features

  • Create errors with error codes to easily classify and handle errors.
  • Wrap errors with additional context such as function parameters and key-value data.
  • Automatically capture call stack information for debugging.
  • Flexible error formatting for both developers and end users.
  • Utility functions to extract error codes, messages, and other metadata from errors.

Installation

To install failure, use the following command:

go get github.com/morikuni/failure/v2

Usage Examples

First, define your application's error codes:

type ErrorCode string

const (
    ErrNotFound ErrorCode = "NotFound"
    ErrInvalidArgument ErrorCode = "InvalidArgument"
)

Use failure.New to create a new error with an error code:

err := failure.New(ErrNotFound, failure.Message("Resource not found"))

Use failure.Wrap to wrap an existing error with additional context:

if err != nil {
    return failure.Wrap(err, failure.Context{"parameter": "value"})
}

Use failure.Is to check for a specific error code and handle the error:

if failure.Is(err, ErrNotFound) {
    // Handle ErrNotFound error
}

Use utility functions to extract metadata from the error:

code := failure.CodeOf(err)
message := failure.MessageOf(err)
callStack := failure.CallStackOf(err)

Example error outputs:

err := failure.New(ErrInvalidArgument, failure.Message("Invalid argument"), failure.Context{"userId": "123"})
fmt.Println(err) 
// Output: GetUser[InvalidArgument](Invalid argument, {userId=123})

fmt.Printf("%+v\n", err)
// Output:
// [main.GetUser] /path/to/file.go:123
//     InvalidArgument
//     Invalid argument
//     {userId=123}
// [CallStack]
//     [main.GetUser] /path/to/file.go:123
//     [main.main] /path/to/main.go:456

For more detailed usage and examples, refer to the Go Reference.

Full Example of Usage

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"

	"github.com/morikuni/failure/v2"
)

type ErrorCode string

const (
	NotFound  ErrorCode = "NotFound"
	Forbidden ErrorCode = "Forbidden"
)

func GetACL(projectID, userID string) (acl interface{}, e error) {
	notFound := true
	if notFound {
		return nil, failure.New(NotFound,
			failure.Context{"project_id": projectID, "user_id": userID},
		)
	}
	return nil, failure.Unexpected("unexpected error")
}

func GetProject(projectID, userID string) (project interface{}, e error) {
	_, err := GetACL(projectID, userID)
	if err != nil {
		if failure.Is(err, NotFound) {
			return nil, failure.Translate(err, Forbidden,
				failure.Message("no acl exists"),
				failure.Context{"additional_info": "hello"},
			)
		}
		return nil, failure.Wrap(err)
	}
	return nil, nil
}

func Handler(w http.ResponseWriter, r *http.Request) {
	_, err := GetProject(r.FormValue("project_id"), r.FormValue("user_id"))
	if err != nil {
		HandleError(w, err)
		return
	}
}

func getHTTPStatus(err error) int {
	switch failure.CodeOf(err) {
	case NotFound:
		return http.StatusNotFound
	case Forbidden:
		return http.StatusForbidden
	default:
		return http.StatusInternalServerError
	}
}

func getMessage(err error) string {
	msg := failure.MessageOf(err)
	if msg != "" {
		return string(msg)
	}
	return "Error"
}

func HandleError(w http.ResponseWriter, err error) {
	w.WriteHeader(getHTTPStatus(err))
	io.WriteString(w, getMessage(err))

	fmt.Println("============ Error ============")
	fmt.Printf("Error = %v\n", err)
	// Error = main.GetProject[Forbidden](no acl exists, {additional_info=hello}): main.GetACL[NotFound]({project_id=aaa,user_id=111})

	code := failure.CodeOf(err)
	fmt.Printf("Code = %v\n", code)
	// Code = Forbidden

	msg := failure.MessageOf(err)
	fmt.Printf("Message = %v\n", msg)
	// Message = no acl exists

	cs := failure.CallStackOf(err)
	fmt.Printf("CallStack = %v\n", cs)
	// CallStack = main.GetACL: main.GetProject: main.Handler: main.main: runtime.main: goexit

	fmt.Printf("Cause = %v\n", failure.CauseOf(err))
	// Cause = main.GetACL[NotFound]({project_id=aaa,user_id=111})

	fmt.Println()
	fmt.Println("============ Detail ============")
	fmt.Printf("%+v\n", err)
	// [main.GetProject] /go/src/github.com/morikuni/failure/example/main.go:34
	//     Forbidden
	//     no acl exists
	//     {additional_info=hello}
	// [main.GetACL] /go/src/github.com/morikuni/failure/example/main.go:23
	//     NotFound
	//     {user_id=111,project_id=aaa}
	// [CallStack]
	//     [main.GetACL] /go/src/github.com/morikuni/failure/example/main.go:23
	//     [main.GetProject] /go/src/github.com/morikuni/failure/example/main.go:31
	//     [main.Handler] /go/src/github.com/morikuni/failure/example/main.go:45
	//     [main.main] /go/src/github.com/morikuni/failure/example/main.go:119
	//     [runtime.main] /opt/homebrew/opt/go/libexec/src/runtime/proc.go:271
	//     [runtime.goexit] /opt/homebrew/opt/go/libexec/src/runtime/asm_arm64.s:1222	
}

func main() {
	req := httptest.NewRequest(http.MethodGet, "/?project_id=aaa&user_id=111", nil)
	rec := httptest.NewRecorder()
	Handler(rec, req)

	res, _ := httputil.DumpResponse(rec.Result(), true)
	fmt.Println("============ Dump ============")
	fmt.Println(string(res))
}

Migration from v1 to v2

See docs/v1-to-v2.md for migration guide.

Contributing

Contributions are welcome! Feel free to send issues or pull requests.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Documentation

Overview

Package failure provides an error handling library for Go. It allows you to create, wrap, and handle errors with additional context and features.

Index

Constants

View Source
const (
	KeyCode key = iota + 1
	KeyContext
	KeyMessage
	KeyCallStack
)

Variables

This section is empty.

Functions

func CauseOf

func CauseOf(err error) error

CauseOf retrieves the cause error in the error chain. The errors wrapped with MarkUnexpected is not returned. To unwrap these errors, use ForceUnwrap in a loop.

func CodeOf

func CodeOf(err error) any

CodeOf retrieves an error code associated with the given error.

func ForceUnwrap

func ForceUnwrap(err error) error

ForceUnwrap unwraps the error, returning the underlying error. If the error does not implement ForceUnwrap method, it uses errors.Unwrap. It is useful for logging the actual cause of the error.

func Is

func Is[C Code](err error, code ...C) bool

Is checks if the error has any of the specified codes. It returns true if a matching code is found.

func MarkUnexpected

func MarkUnexpected(err error, fields ...Field) error

MarkUnexpected creates a new error that cannot be unwrapped using the standard Unwrap method. Use this function when you want to prevent propagating data like error codes or context to the caller. However, using ForceUnwrap will still allow retrieving the original error.

func New

func New[C Code](c C, fields ...Field) error

New creates a new error with the provided error code and optional fields.

func OriginValue

func OriginValue[K comparable](err error, key K) any

OriginValue retrieves the original (firstly set) value associated with the specified key from the given error. It forcefully unwraps the error using ForceUnwrap until it finds a matching key or reaches the end of the error chain.

func OriginValueAs

func OriginValueAs[V any, K comparable](err error, key K) (zero V, _ bool)

OriginValueAs is utility for OriginValue that also asserts that the value has the specified type V. If the value is not of the expected type, it panics.

func Translate

func Translate[C Code](err error, c C, fields ...Field) error

Translate creates a new error by translating the error code of an existing error. It wraps the original error with the new error code and optional fields.

func Unexpected

func Unexpected(text string, fields ...Field) error

Unexpected creates a new error with the provided text and optional fields. Use this function when you want to create without an error code. This function should only be used when the error is not expected to occur.

func Value

func Value[K comparable](err error, key K) any

Value retrieves the value associated with the specified key from the given error. It unwraps the error until it finds a matching key or reaches the end of the error chain.

func ValueAs

func ValueAs[V any, K comparable](err error, key K) (zero V, _ bool)

ValueAs is utility for Value that also asserts that the value has the specified type V. If the value is not of the expected type, it panics.

func Wrap

func Wrap(err error, fields ...Field) error

Wrap creates a new error by wrapping an existing error with optional fields. It does not change the error code of the original error.

Types

type CallStack

type CallStack []uintptr

CallStack represents a stack of program counters. It implements the Field interface.

func CallStackOf

func CallStackOf(err error) CallStack

CallStackOf retrieves a CallStack associated with the given error.

func Callers

func Callers(skip int) CallStack

Callers returns a CallStack of the caller's goroutine stack. The skip parameter determines the number of stack frames to skip before capturing the CallStack.

func NewCallStack

func NewCallStack(pcs []uintptr) CallStack

NewCallStack creates a new CallStack from the provided program counters.

func (CallStack) Format

func (cs CallStack) Format(s fmt.State, verb rune)

Format implements the fmt.Formatter interface.

func (CallStack) Frames

func (cs CallStack) Frames() []Frame

Frames is a method of CallStack that returns a slice of Frame objects representing the CallStack's frames.

func (CallStack) HeadFrame

func (cs CallStack) HeadFrame() Frame

HeadFrame is a method of CallStack that returns the first frame in the CallStack. If the CallStack is empty, it returns an empty frame.

func (CallStack) SetErrorField

func (cs CallStack) SetErrorField(setter FieldSetter)

SetErrorField implements the Field interface.

type Code

type Code comparable

Code represents an error code. Any comparable type can be used as an error code.

type Context

type Context map[string]string

Context represents additional contextual information associated with an error. It implements the Field interface.

func (Context) FormatError

func (c Context) FormatError(w ErrorWriter)

FormatError implements the ErrorFormatter interface.

func (Context) SetErrorField

func (c Context) SetErrorField(setter FieldSetter)

SetErrorField implements the Field interface.

type ErrorFormatter

type ErrorFormatter interface {
	FormatError(ErrorWriter)
}

ErrorFormatter is an interface for formatting errors. Implement this interface to format errors in custom ways.

type ErrorWriter

type ErrorWriter interface {
	io.Writer
}

ErrorWriter is used by ErrorFormatter to write errors with custom formats. It may have additional fields to specify output format in the future.

type Failure

type Failure interface {
	error
	Value(key any) any

	fmt.Formatter
	Unwrap() error
	As(target any) bool
	// contains filtered or unexported methods
}

Failure represents a error with additional information. It cannot be implemented by external types, but can be embedded within custom structs to implement custom methods.

func ForceUnwrapFailure

func ForceUnwrapFailure(err error) (_ Failure, tail error)

ForceUnwrapFailure force unwraps the error, returning the first Failure found in the error chain and the remaining tail of the error. Unlike UnwrapFailure, it pops Failure even if the error is opaqued.

func NewFailure

func NewFailure(underlying error, fieldsSet ...[]Field) Failure

NewFailure creates a new Failure from an underlying error and optional fields. It panics if both the underlying error and fields are empty.

func UnwrapFailure

func UnwrapFailure(err error) (_ Failure, tail error)

UnwrapFailure unwraps the error, returning the first Failure found in the error chain and the remaining tail of the error.

type Field

type Field interface {
	SetErrorField(FieldSetter)
}

Field represents an error field. Implement this interface to define your own error fields and attach them to your errors.

func WithCode

func WithCode[C Code](c C) Field

WithCode creates a new Field with the provided code. Generally, New and Translate functions should be used instead of this function. This function is useful when you create your own error wrapper functions.

type FieldSetter

type FieldSetter interface {
	Set(key, value any)
}

FieldSetter is used by Field to set key-value pairs to an error.

type Frame

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

Frame represents a single frame in a CallStack.

func (Frame) File

func (f Frame) File() string

File returns the base file name associated with the Frame.

func (Frame) Format

func (f Frame) Format(s fmt.State, verb rune)

Format implements the fmt.Formatter interface.

func (Frame) Func

func (f Frame) Func() string

Func returns the function name associated with the Frame.

func (Frame) Line

func (f Frame) Line() int

Line returns the line number of the file.

func (Frame) PC

func (f Frame) PC() uintptr

PC returns the program counter associated with the Frame

func (Frame) Path

func (f Frame) Path() string

Path returns the full path of the file associated with the Frame.

func (Frame) Pkg

func (f Frame) Pkg() string

Pkg returns the package name associated with the Frame. It is the last element of the PkgPath.

func (Frame) PkgPath

func (f Frame) PkgPath() string

PkgPath returns the package path associated with the Frame.

type Message

type Message string

Message represents a human-readable error message. It implements the Field interface.

func MessageOf

func MessageOf(err error) Message

MessageOf retrieves a Message associated with the given error.

func Messagef

func Messagef(format string, a ...any) Message

Messagef creates a new Message with the provided format and arguments.

func (Message) SetErrorField

func (m Message) SetErrorField(setter FieldSetter)

SetErrorField implements the Field interface.

func (Message) String

func (m Message) String() string

Jump to

Keyboard shortcuts

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