Package failure
is an error handling library for Go. It allows you to create, wrap, and handle errors with additional context and 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.
To install failure
, use the following command:
go get github.com/morikuni/failure/v2
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.
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))
}
See docs/v1-to-v2.md for migration guide.
Contributions are welcome! Feel free to send issues or pull requests.
This project is licensed under the MIT License. See the LICENSE file for details.