Skip to content

Commit

Permalink
Merge pull request #50 from morikuni/v2
Browse files Browse the repository at this point in the history
failure v2
  • Loading branch information
morikuni authored Apr 19, 2024
2 parents 5ed04a9 + 1e0483b commit 2551069
Show file tree
Hide file tree
Showing 24 changed files with 968 additions and 1,275 deletions.
20 changes: 0 additions & 20 deletions .circleci/config.yml

This file was deleted.

12 changes: 0 additions & 12 deletions .codecov.yml

This file was deleted.

20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: test

on:
push:
branches: [ main ]
pull_request:
types: [ opened, synchronize, reopened ]

jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: reviewdog/action-golangci-lint@v2
with:
reporter: github-pr-review
filter_mode: diff_context
fail_on_error: true
- run: make test
14 changes: 0 additions & 14 deletions .gitignore

This file was deleted.

15 changes: 4 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
.PHONY: test
test:
GO111MODULE=on go test -v ./... -count 1
go test -v ./... -count 1

.PHONY: cover
cover:
GO111MODULE=on go test -coverpkg=. -covermode=atomic -coverprofile=coverage.txt

.PHONY: view-cover
view-cover: cover
GO111MODULE=on go tool cover -html coverage.txt

mod:
GO111MODULE=on go mod tidy
.PHONY: lint
lint:
go run github.com/golangci/golangci-lint/cmd/golangci-lint@latest run ./...
191 changes: 93 additions & 98 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,106 +1,90 @@
# failure

[![CircleCI](https://circleci.com/gh/morikuni/failure/tree/main.svg?style=shield)](https://circleci.com/gh/morikuni/failure/tree/master)
[![Go Reference](https://pkg.go.dev/badge/github.com/morikuni/failure.svg)](https://pkg.go.dev/github.com/morikuni/failure)
[![Go Report Card](https://goreportcard.com/badge/github.com/morikuni/failure)](https://goreportcard.com/report/github.com/morikuni/failure)
[![codecov](https://codecov.io/gh/morikuni/failure/branch/main/graph/badge.svg)](https://codecov.io/gh/morikuni/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` provides errors utilities for your application errors.
Package `failure` is an error handling library for Go. It allows you to create, wrap, and handle errors with additional context and features.

- Automatically generate awesome `err.Error` message for developers.
- Flexible error messages for end users.
- Powerful and readable stack trace.
- Error context, such as function parameter, with key-value data.
- Extensible error chain.
## Features

## Usage
- 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.

At first, define error codes for your application.
## Installation

To install `failure`, use the following command:

```go
const (
NotFound failure.StringCode = "NotFound"
InvalidArgument failure.StringCode = "InvalidArgument"
Internal failure.StringCode = "Internal"
)
```
go get github.com/morikuni/failure/v2
```

## Usage Examples

Using `failure.New`, return an error with error code.
First, define your application's error codes:

```go
return failure.New(NotFound)
type ErrorCode string

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

Handle the error with `failure.Is` and translate it into another error code with `failure.Translate`.
Use `failure.New` to create a new error with an error code:

```go
if failure.Is(err, NotFound) {
return failure.Translate(err, Internal)
}
err := failure.New(ErrNotFound, failure.Message("Resource not found"))
```

If you want to just return the error, use `failure.Wrap`.
Use `failure.Wrap` to wrap an existing error with additional context:

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

An error context and message for end user can be attached.
Use `failure.Is` to check for a specific error code and handle the error:

```go
func Foo(a, b string) error {
return failure.New(InvalidArgument,
failure.Context{"a": a, "b": b},
failure.Message("Given parameters are invalid!!"),
)
if failure.Is(err, ErrNotFound) {
// Handle ErrNotFound error
}
```

Awesome error message for developers.
Use utility functions to extract metadata from the error:

```go
func main() {
err := Bar()
fmt.Println(err)
fmt.Println("=====")
fmt.Printf("%+v\n", err)
}

func Bar() error {
err := Foo("hello", "world")
if err != nil {
return failure.Wrap(err)
}
return nil
}
code := failure.CodeOf(err)
message := failure.MessageOf(err)
callStack := failure.CallStackOf(err)
```

```
main.Bar: main.Foo: a=hello b=world: Given parameters are invalid!!: code(InvalidArgument)
=====
[main.Bar] /tmp/sandbox615088634/prog.go:25
[main.Foo] /tmp/sandbox615088634/prog.go:31
a = hello
b = world
message("Given parameters are invalid!!")
code(InvalidArgument)
[CallStack]
[main.Foo] /tmp/sandbox615088634/prog.go:31
[main.Bar] /tmp/sandbox615088634/prog.go:23
[main.main] /tmp/sandbox615088634/prog.go:16
[runtime.main] /usr/local/go-faketime/src/runtime/proc.go:204
[runtime.goexit] /usr/local/go-faketime/src/runtime/asm_amd64.s:1374
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
```

`package.FunctionName` like `main.Bar` and `main.Foo` is automatically added to error message.
With `%+v` format, it prints the detailed error chain + the call stack of the first error.
For more detailed usage and examples, refer to the [Go Reference](https://pkg.go.dev/github.com/morikuni/failure).

## Full Example for HTTP Server
## Full Example of Usage

Try it on [The Go Playground](https://play.golang.org/p/Pmgm7-7J1_c)!

```go
package main

Expand All @@ -111,13 +95,14 @@ import (
"net/http/httptest"
"net/http/httputil"

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

// error codes for your application.
type ErrorCode string

const (
NotFound failure.StringCode = "NotFound"
Forbidden failure.StringCode = "Forbidden"
NotFound ErrorCode = "NotFound"
Forbidden ErrorCode = "Forbidden"
)

func GetACL(projectID, userID string) (acl interface{}, e error) {
Expand Down Expand Up @@ -153,11 +138,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
}

func getHTTPStatus(err error) int {
c, ok := failure.CodeOf(err)
if !ok {
return http.StatusInternalServerError
}
switch c {
switch failure.CodeOf(err) {
case NotFound:
return http.StatusNotFound
case Forbidden:
Expand All @@ -168,9 +149,9 @@ func getHTTPStatus(err error) int {
}

func getMessage(err error) string {
msg, ok := failure.MessageOf(err)
if ok {
return msg
msg := failure.MessageOf(err)
if msg != "" {
return string(msg)
}
return "Error"
}
Expand All @@ -181,38 +162,40 @@ func HandleError(w http.ResponseWriter, err error) {

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)
code := failure.CodeOf(err)
fmt.Printf("Code = %v\n", code)
// Code = Forbidden

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

cs, _ := failure.CallStackOf(err)
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:36
// message("no acl exists")
// additional_info = hello
// code(Forbidden)
// [main.GetACL] /go/src/github.com/morikuni/failure/example/main.go:21
// project_id = 123
// user_id = 456
// code(NotFound)
// [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:21
// [main.GetProject] /go/src/github.com/morikuni/failure/example/main.go:33
// [main.Handler] /go/src/github.com/morikuni/failure/example/main.go:47
// [http.HandlerFunc.ServeHTTP] /usr/local/go/src/net/http/server.go:1964
// [http.(*ServeMux).ServeHTTP] /usr/local/go/src/net/http/server.go:2361
// [http.serverHandler.ServeHTTP] /usr/local/go/src/net/http/server.go:2741
// [http.(*conn).serve] /usr/local/go/src/net/http/server.go:1847
// [runtime.goexit] /usr/local/go/src/runtime/asm_amd64.s:1333
// [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() {
Expand All @@ -225,3 +208,15 @@ func main() {
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.
Loading

0 comments on commit 2551069

Please sign in to comment.