Skip to content

Commit

Permalink
update response model for Error
Browse files Browse the repository at this point in the history
  • Loading branch information
zekroTJA committed Aug 7, 2024
1 parent 0a6bb5c commit f27996b
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 54 deletions.
6 changes: 3 additions & 3 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func Cast(err error, fallback ...ErrorCode) Error {
return *lastElkErr
}

d, ok := As[Error](err)
d, ok := err.(Error)
if !ok {
d = Wrap(code, err)
d.callStack.offset++
Expand Down Expand Up @@ -142,9 +142,9 @@ func WrapCopyCode(err error, message ...string) Error {
return e
}

// WrapCopyCode wraps the error with a message formatted according to the given
// WrapCopyCodef wraps the error with a message formatted according to the given
// format specification keeping the error code of the wrapped error. If the
// wrapped error does not have a error code, CodeUnexpected is set insetad.
// wrapped error does not have a error code, CodeUnexpected is set instead.
func WrapCopyCodef(err error, format string, a ...any) Error {
e := WrapCopyCode(err, fmt.Sprintf(format, a...))
e.callStack.offset++
Expand Down
69 changes: 56 additions & 13 deletions example_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,38 +63,81 @@ func ExampleIsOfType() {
// err: true
}

type DetailedError struct {
elk.InnerError
details any
}

func (t DetailedError) Details() any {
return t.details
}

func ExampleJson() {
strErr := errors.New("some error")
dErr := elk.Wrap(elk.ErrorCode("some-error-code"), strErr, "some message")
mErr := elk.Wrap("some-error-code", strErr, "some message")

json, _ := elk.Json(strErr, 0)
fmt.Println(string(json))

json, _ = elk.Json(strErr, 400)
fmt.Println(string(json))

json, _ := elk.Json(strErr)
json, _ = elk.Json(mErr, 0)
fmt.Println(string(json))

json, _ = elk.Json(strErr, true)
json, _ = elk.Json(mErr, 400)
fmt.Println(string(json))

json, _ = elk.Json(dErr, true)
dtErr := DetailedError{}
dtErr.Inner = elk.NewError("some-error", "an error with details")
dtErr.details = struct {
Foo string
Bar int
}{
Foo: "foo",
Bar: 123,
}

json, _ = elk.Json(dtErr, 500)
fmt.Println(string(json))

json, _ = elk.Json(dErr)
dteErr := elk.Wrap("some-detailed-error-wrapped", dtErr, "some detailed error wrapped")
json, _ = elk.Json(dteErr, 500)
fmt.Println(string(json))

// Output:
// {
// "error": "internal error"
// "Code": "unexpected-error"
// }
// {
// "Code": "unexpected-error",
// "Status": 400
// }
// {
// "Code": "some-error-code",
// "Message": "some message"
// }
// {
// "error": "some error"
// "Code": "some-error-code",
// "Message": "some message",
// "Status": 400
// }
// {
// "error": "some error",
// "code": "some-error-code",
// "message": "some message"
// "Code": "unexpected-error",
// "Status": 500,
// "Details": {
// "Foo": "foo",
// "Bar": 123
// }
// }
// {
// "error": "internal error",
// "code": "some-error-code",
// "message": "some message"
// "Code": "some-detailed-error-wrapped",
// "Message": "some detailed error wrapped",
// "Status": 500,
// "Details": {
// "Foo": "foo",
// "Bar": 123
// }
// }
}

Expand Down
16 changes: 9 additions & 7 deletions examples/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func main() {

mux.HandleFunc("/count", handleCount(ctl))

http.ListenAndServe(":8080", mux)
_ = http.ListenAndServe(":8080", mux)
}

func handleCount(ctl *Controller) http.HandlerFunc {
Expand All @@ -42,19 +42,21 @@ func handleGetCount(ctl *Controller, w http.ResponseWriter, r *http.Request) {

res, err := ctl.GetCount(id)
if err != nil {
var status int
switch elk.Cast(err).Code() {
case ErrorCountNotFound:
w.WriteHeader(http.StatusNotFound)
status = http.StatusNotFound
default:
log.Printf("error: %+.5v\n", err)
w.WriteHeader(http.StatusInternalServerError)
status = http.StatusInternalServerError
}
w.Write(elk.MustJson(err))
w.WriteHeader(status)
_, _ = w.Write(elk.MustJson(err, status))
return
}

d, _ := json.MarshalIndent(res, "", " ")
w.Write(d)
_, _ = w.Write(d)
}

func handlePostCount(ctl *Controller, w http.ResponseWriter, r *http.Request) {
Expand All @@ -71,10 +73,10 @@ func handlePostCount(ctl *Controller, w http.ResponseWriter, r *http.Request) {
log.Printf("error: %#.5v\n", err)
w.WriteHeader(http.StatusInternalServerError)
}
w.Write(elk.MustJson(err))
_, _ = w.Write(elk.MustJson(err, http.StatusInternalServerError))
return
}

d, _ := json.MarshalIndent(res, "", " ")
w.Write(d)
_, _ = w.Write(d)
}
6 changes: 6 additions & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ type HasCode interface {
Code() ErrorCode
}

type HasDetails interface {
error

Details() any
}

// HasCode describes an error which has a
// CallStack.
type HasCallStack interface {
Expand Down
61 changes: 30 additions & 31 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,28 @@ func IsOfType[T error](err error) bool {
return false
}

type errorJsonModel struct {
Error string `json:"error"`
Code ErrorCode `json:"code,omitempty"`
Message string `json:"message,omitempty"`
Details any `json:"details,omitempty"`
// ErrorResponseModel is used to encode an Error into an API response.
type ErrorResponseModel struct {
Code ErrorCode // The error code
Message string `json:",omitempty"` // An optional short message to further specify the error
Status int `json:",omitempty"` // An optional platform- or protocol-specific status code; i.e. HTTP status code
Details any `json:",omitempty"` // Optional additional detailed context for the error
}

// ToResponseModel transforms the
func (t Error) ToResponseModel(statusCode int) (model ErrorResponseModel) {
model.Status = statusCode
model.Code = t.Code()

if mErr, ok := As[HasMessage](t); ok {
model.Message = mErr.Message()
}

if dErr, ok := As[HasDetails](t); ok {
model.Details = dErr.Details()
}

return model
}

// Json takes an error and marshals it into
Expand All @@ -81,26 +98,8 @@ type errorJsonModel struct {
//
// When the JSON marshal fails, an error is
// returned.
func Json(err error, exposeError ...bool) ([]byte, error) {
var model errorJsonModel

if len(exposeError) > 0 && exposeError[0] {
if inner := errors.Unwrap(err); inner != nil {
model.Error = inner.Error()
} else {
model.Error = err.Error()
}
} else {
model.Error = "internal error"
}

if mErr, ok := err.(HasMessage); ok {
model.Message = mErr.Message()
}

if cErr, ok := err.(HasCode); ok {
model.Code = cErr.Code()
}
func Json(err error, statusCode int) ([]byte, error) {
model := Cast(err).ToResponseModel(statusCode)

data, jErr := json.MarshalIndent(model, "", " ")
if jErr != nil {
Expand All @@ -112,23 +111,23 @@ func Json(err error, exposeError ...bool) ([]byte, error) {

// MustJson is an alias for Json but panics when
// the call to Json returns an error.
func MustJson(err error) []byte {
return mustV(Json(err))
func MustJson(err error, statusCode int) []byte {
return mustV(Json(err, statusCode))
}

// JsonString behaves the same as Json() but returns the result as string instead
// of a slice of bytes.
func JsonString(err error, exposeError ...bool) (string, error) {
res, err := Json(err, exposeError...)
func JsonString(err error, statusCode int) (string, error) {
res, err := Json(err, statusCode)
if err != nil {
return "", err
}
return string(res), nil
}

// MustJsonString is an alias for JsonString but panics when the call to Json returns an error.
func MustJsonString(err error) string {
return mustV(JsonString(err))
func MustJsonString(err error, statusCode int) string {
return mustV(JsonString(err, statusCode))
}

func mustV[TV any](v TV, err error) TV {
Expand Down

0 comments on commit f27996b

Please sign in to comment.