Add JSON marshalling

This commit is contained in:
Dan Jones 2024-01-21 13:50:13 -06:00
commit 2895433239
3 changed files with 54 additions and 2 deletions

View file

@ -1,6 +1,7 @@
package errors package errors
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
) )
@ -16,13 +17,14 @@ import (
// //
// err := errors.Errorf(http.StatusNotFound, "%w", sqlError).SetMsg("user not found %d", userId) // err := errors.Errorf(http.StatusNotFound, "%w", sqlError).SetMsg("user not found %d", userId)
func Errorf(status int, format string, parts ...any) SettableError { func Errorf(status int, format string, parts ...any) SettableError {
meta := make(map[string]any)
if len(parts) == 0 { if len(parts) == 0 {
return &erf{status, format, ""} return &erf{status, format, "", meta}
} }
err := fmt.Errorf(format, parts...) err := fmt.Errorf(format, parts...)
msg := err.Error() msg := err.Error()
er := &erf{status, msg, ""} er := &erf{status, msg, "", meta}
if we, ok := err.(wrappedError); ok { if we, ok := err.(wrappedError); ok {
return &wrapErr{er, we.Unwrap()} return &wrapErr{er, we.Unwrap()}
} }
@ -34,11 +36,13 @@ func Errorf(status int, format string, parts ...any) SettableError {
var _ ResponsableError = new(erf) var _ ResponsableError = new(erf)
var _ SettableError = new(erf) var _ SettableError = new(erf)
var _ json.Marshaler = new(erf)
type erf struct { type erf struct {
stat int stat int
err string err string
msg string msg string
meta map[string]any
} }
func (e *erf) Status() int { func (e *erf) Status() int {
@ -76,6 +80,21 @@ func (e *erf) SetMsg(msg string, parts ...any) SettableError {
return e return e
} }
func (e *erf) SetField(field string, value any) SettableError {
e.meta[field] = value
return e
}
func (e *erf) JSON() any {
m := e.meta
m["error"] = e.Msg()
return m
}
func (e *erf) MarshalJSON() ([]byte, error) {
return json.Marshal(e.JSON())
}
var _ UnwrappableError = new(wrapErr) var _ UnwrappableError = new(wrapErr)
type wrapErr struct { type wrapErr struct {

View file

@ -2,6 +2,7 @@ package errors
import ( import (
"errors" "errors"
"encoding/json"
"net/http" "net/http"
"testing" "testing"
@ -102,3 +103,24 @@ func (s *ErrorfTestSuite) TestSetStatusOutsideRange() {
err.SetStatus(600) err.SetStatus(600)
s.Assert().Equal(http.StatusPaymentRequired, err.Status()) s.Assert().Equal(http.StatusPaymentRequired, err.Status())
} }
func (s *ErrorfTestSuite) TestJSON() {
var err SettableError = Errorf(http.StatusPaymentRequired, "Hello")
s.Assert().NotNil(err)
m := err.JSON()
ma, _ := m.(map[string]any)
s.Assert().Equal("Hello", ma["error"])
s.Assert().Nil(ma["number"])
j, _ := json.Marshal(err)
s.Assert().Equal(`{"error":"Hello"}`, string(j))
err.SetField("number",42)
m = err.JSON()
ma, _ = m.(map[string]any)
s.Assert().Equal(42, ma["number"])
j, _ = json.Marshal(err)
s.Assert().Equal(`{"error":"Hello","number":42}`, string(j))
}

View file

@ -1,5 +1,7 @@
package errors package errors
import "encoding/json"
// ResponsableError is an error that has information useful in an HTTP response. // ResponsableError is an error that has information useful in an HTTP response.
// The string returned by Error should be suitable for logging useful information // The string returned by Error should be suitable for logging useful information
// to assist debugging the error. // to assist debugging the error.
@ -8,10 +10,16 @@ package errors
// //
// Msg should return a message suitable to display to the end user. If the message // Msg should return a message suitable to display to the end user. If the message
// returned by Error is safe for the end user, it may simply call that. // returned by Error is safe for the end user, it may simply call that.
//
// JSON should return something that can be marshalled to JSON for the response body.
// It can be something as simple as map[string]string{"error":err.Msg()}.
// JSON's return value should be used by MarshalJSON()
type ResponsableError interface { type ResponsableError interface {
json.Marshaler
Error() string Error() string
Status() int Status() int
Msg() string Msg() string
JSON() any
} }
// SettableError is a ResponsableError which can be modified after initial creation. // SettableError is a ResponsableError which can be modified after initial creation.
@ -22,10 +30,13 @@ type ResponsableError interface {
// Methods are chainable. // Methods are chainable.
// //
// SetStatus should not use a value outside of 100-599. It may either ignore such values, or panic. // SetStatus should not use a value outside of 100-599. It may either ignore such values, or panic.
//
// SetField should add some metadata to the error, which will be included in the data returned by JSON()
type SettableError interface { type SettableError interface {
ResponsableError ResponsableError
SetStatus(int) SettableError SetStatus(int) SettableError
SetMsg(string, ...any) SettableError SetMsg(string, ...any) SettableError
SetField(string, any) SettableError
} }
// UnwrappableError allows a ResponsableError to wrap another error. // UnwrappableError allows a ResponsableError to wrap another error.