Skip to content

Latest commit

 

History

History
222 lines (173 loc) · 5.69 KB

README.md

File metadata and controls

222 lines (173 loc) · 5.69 KB

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.