Skip to content

Commit

Permalink
feat: add ErrorWithDetail
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanhitt committed Nov 21, 2024
1 parent 64a2a9d commit 8b4c4df
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 6 deletions.
21 changes: 18 additions & 3 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ type ErrorWithStatus interface {
StatusCode() int
}

type ErrorWithDetail interface {
error
DetailMsg() string
}

// HTTPError is the error response used by the serialization part of the framework.
type HTTPError struct {
Err error `json:"-" xml:"-"` // Developer readable error message. Not shown to the user to avoid security leaks.
Expand All @@ -40,7 +45,7 @@ func (e HTTPError) Error() string {
title = "HTTP Error"
}
}
return fmt.Sprintf("%d %s: %s", code, title, e.Detail)
return fmt.Sprintf("%d %s: %s", code, title, e.DetailMsg())
}

func (e HTTPError) StatusCode() int {
Expand All @@ -50,6 +55,10 @@ func (e HTTPError) StatusCode() int {
return e.Status
}

func (e HTTPError) DetailMsg() string {
return e.Detail
}

func (e HTTPError) Unwrap() error { return e.Err }

// BadRequestError is an error used to return a 400 status code.
Expand Down Expand Up @@ -123,7 +132,7 @@ func (e NotAcceptableError) Unwrap() error { return HTTPError(e) }

// ErrorHandler is the default error handler used by the framework.
// It transforms any error into the unified error type [HTTPError],
// Using the [ErrorWithStatus] interface.
// Using the [ErrorWithStatus] and [ErrorWithDetail] interface.
func ErrorHandler(err error) error {
errResponse := HTTPError{
Err: err,
Expand All @@ -141,11 +150,17 @@ func ErrorHandler(err error) error {
errResponse.Status = errorStatus.StatusCode()
}

// Check for detail
var errorDetail ErrorWithDetail
if errors.As(err, &errorDetail) {
errResponse.Detail = errorDetail.DetailMsg()
}

if errResponse.Title == "" {
errResponse.Title = http.StatusText(errResponse.Status)
}

slog.Error("Error "+errResponse.Title, "status", errResponse.StatusCode(), "detail", errResponse.Detail, "error", errResponse.Err)
slog.Error("Error "+errResponse.Title, "status", errResponse.StatusCode(), "detail", errResponse.DetailMsg(), "error", errResponse.Err)

return errResponse
}
23 changes: 20 additions & 3 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ import (

type myError struct {
status int
err HTTPError
detail string
}

var _ ErrorWithStatus = myError{}
var _ ErrorWithDetail = myError{}

func (e myError) Error() string { return "test error" }
func (e myError) StatusCode() int { return e.status }
func (e myError) Error() string { return "test error" }
func (e myError) StatusCode() int { return e.status }
func (e myError) DetailMsg() string { return e.detail }
func (e myError) Unwrap() error { return HTTPError(e.err) }

func TestErrorHandler(t *testing.T) {
t.Run("basic error", func(t *testing.T) {
Expand All @@ -39,7 +44,7 @@ func TestErrorHandler(t *testing.T) {
require.Equal(t, http.StatusNotFound, errResponse.(HTTPError).StatusCode())
})

t.Run("error with status ", func(t *testing.T) {
t.Run("error with status", func(t *testing.T) {
err := myError{
status: http.StatusNotFound,
}
Expand All @@ -50,6 +55,18 @@ func TestErrorHandler(t *testing.T) {
require.Equal(t, http.StatusNotFound, errResponse.(HTTPError).StatusCode())
})

t.Run("error with detail", func(t *testing.T) {
err := myError{
detail: "my detail",
}
errResponse := ErrorHandler(err)
require.ErrorAs(t, errResponse, &HTTPError{})
require.Contains(t, errResponse.Error(), "Internal Server Error")
require.Contains(t, errResponse.Error(), "500")
require.Contains(t, errResponse.Error(), "my detail")
require.Equal(t, http.StatusInternalServerError, errResponse.(HTTPError).StatusCode())
})

t.Run("conflict error", func(t *testing.T) {
err := ConflictError{
Err: errors.New("Conflict"),
Expand Down
20 changes: 20 additions & 0 deletions examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ type MyResponse struct {
BestFramework string `json:"best"`
}

type MyError struct {
Err error `json:"error"`
Message string `json:"message"`
}

var _ fuego.ErrorWithStatus = MyError{}
var _ fuego.ErrorWithDetail = MyError{}

func (e MyError) Error() string { return e.Err.Error() }

func (e MyError) StatusCode() int { return http.StatusTeapot }

func (e MyError) DetailMsg() string {
return strings.Split(e.Error(), " ")[1]
}

func main() {
s := fuego.NewServer(
fuego.WithAddr("localhost:8088"),
Expand Down Expand Up @@ -59,6 +75,10 @@ func main() {
w.Write([]byte("Hello, World!"))
})

fuego.Get(s, "/custom-err", func(c *fuego.ContextNoBody) (string, error) {
return "hello", MyError{Err: errors.New("my error")}
})

s.Run()
}

Expand Down

0 comments on commit 8b4c4df

Please sign in to comment.