An open API service indexing awesome lists of open source software.

https://github.com/morikuni/failure

failure is a utility package for handling application errors.
https://github.com/morikuni/failure

error error-handling go golang

Last synced: 3 months ago
JSON representation

failure is a utility package for handling application errors.

Awesome Lists containing this project

README

          

# failure

[![Go Reference](https://pkg.go.dev/badge/github.com/morikuni/failure/v2.svg)](https://pkg.go.dev/github.com/morikuni/failure/v2)

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:

```go
type ErrorCode string

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

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

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

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

```go
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:

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

Use utility functions to extract metadata from the error:

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

Example error outputs:

```go
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](https://pkg.go.dev/github.com/morikuni/failure).

## Full Example of Usage

```go
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](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](LICENSE) file for details.