Skip to content

Commit

Permalink
feat: json and str formats support with custom error type
Browse files Browse the repository at this point in the history
  • Loading branch information
sum2000 committed Dec 25, 2019
1 parent 14b5c19 commit dfafd27
Show file tree
Hide file tree
Showing 8 changed files with 586 additions and 220 deletions.
31 changes: 16 additions & 15 deletions eris.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,6 @@ func Cause(err error) error {
}
}

func formatError(err error, s fmt.State, verb rune) {
var withTrace bool
switch verb {
case 'v':
if s.Flag('+') {
withTrace = true
}
}
f := NewDefaultFormat(withTrace)
p := NewDefaultPrinter(f)
io.WriteString(s, p.Sprint(err))
}

type rootError struct {
msg string
stack *stack
Expand All @@ -136,7 +123,7 @@ func (e *rootError) Error() string {
}

func (e *rootError) Format(s fmt.State, verb rune) {
formatError(e, s, verb)
printError(e, s, verb)
}

func (e *rootError) Is(target error) bool {
Expand All @@ -157,7 +144,7 @@ func (e *wrapError) Error() string {
}

func (e *wrapError) Format(s fmt.State, verb rune) {
formatError(e, s, verb)
printError(e, s, verb)
}

func (e *wrapError) Is(target error) bool {
Expand All @@ -170,3 +157,17 @@ func (e *wrapError) Is(target error) bool {
func (e *wrapError) Unwrap() error {
return e.err
}

func printError(err error, s fmt.State, verb rune) {
var withTrace bool
switch verb {
case 'v':
if s.Flag('+') {
withTrace = true
}
}
format := NewDefaultFormat(withTrace)
uErr := Unpack(err)
str := uErr.ToString(format)
io.WriteString(s, str)
}
9 changes: 5 additions & 4 deletions eris_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,10 @@ func TestErrorFormatting(t *testing.T) {
t.Errorf("%v: expected { %v } got { %v }", desc, tc.output, err)
}

// todo: not sure how to automate stack trace verification
fmt.Printf("error formatting results (%v):\n", desc)
fmt.Printf("%v\n", err)
fmt.Printf("%+v", err)
// todo: automate stack trace verification
fmt.Sprintf("error formatting results (%v):\n", desc)
fmt.Sprintf("%v\n", err)
fmt.Sprintf("%+v", err)

}
}
196 changes: 196 additions & 0 deletions format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package eris

import (
"fmt"
)

// Format defines an error output format to be used with the default formatter.
type Format struct {
WithTrace bool // Flag that enables stack trace output.
Msg string // Separator between error messages and stack frame data.
TBeg string // Separator at the beginning of each stack frame.
TSep string // Separator between elements of each stack frame.
Sep string // Separator between each error in the chain.
}

// NewDefaultFormat conveniently returns a basic format for the default string formatter.
func NewDefaultFormat(withTrace bool) Format {
stringFmt := Format{
WithTrace: withTrace,
Sep: ": ",
}
if withTrace {
stringFmt.Msg = "\n"
stringFmt.TBeg = "\t"
stringFmt.TSep = ": "
stringFmt.Sep = "\n"
}
return stringFmt
}

// UnpackedError represents complete information about an error.
//
// This type can be used for custom error logging and parsing. Use `eris.Unpack` to build an UnpackedError
// from any error type. The ErrChain and ErrRoot fields correspond to `wrapError` and `rootError` types,
// respectively. If any other error type is unpacked, it will appear in the ExternalErr field.
type UnpackedError struct {
ErrChain *[]ErrLink
ErrRoot *ErrRoot
ExternalErr string
}

// Unpack returns UnpackedError type for a given golang error type.
func Unpack(err error) UnpackedError {
e := UnpackedError{}
switch err.(type) {
case nil:
return UnpackedError{}
case *rootError:
e = unpackRootErr(err.(*rootError))
case *wrapError:
chain := []ErrLink{}
e = unpackWrapErr(&chain, err.(*wrapError))
default:
e.ExternalErr = err.Error()
}
return e
}

// ToString returns a default formatted string for a given eris error.
func (upErr *UnpackedError) ToString(format Format) string {
var str string
if upErr.ErrChain != nil {
for _, eLink := range *upErr.ErrChain {
str += eLink.formatStr(format)
}
}
str += upErr.ErrRoot.formatStr(format)
if upErr.ExternalErr != "" {
str += fmt.Sprint(upErr.ExternalErr)
}
return str
}

// ToJSON returns a JSON formatted map for a given eris error.
func (upErr *UnpackedError) ToJSON(format Format) map[string]interface{} {
if upErr == nil {
return nil
}
jsonMap := make(map[string]interface{})
if fmtRootErr := upErr.ErrRoot.formatJSON(format); fmtRootErr != nil {
jsonMap["error root"] = fmtRootErr
}
if upErr.ErrChain != nil {
var wrapArr []map[string]interface{}
for _, eLink := range *upErr.ErrChain {
wrapMap := eLink.formatJSON(format)
wrapArr = append(wrapArr, wrapMap)
}
jsonMap["error chain"] = wrapArr
}
if upErr.ExternalErr != "" {
jsonMap["external error"] = fmt.Sprint(upErr.ExternalErr)
}
return jsonMap
}

func unpackRootErr(err *rootError) UnpackedError {
return UnpackedError{
ErrRoot: &ErrRoot{
Msg: err.msg,
Stack: err.stack.get(),
},
}
}

func unpackWrapErr(chain *[]ErrLink, err *wrapError) UnpackedError {
link := ErrLink{}
link.Frame = *err.frame.get()
link.Msg = err.msg
*chain = append(*chain, link)

e := UnpackedError{}
e.ErrChain = chain

nextErr := err.Unwrap()
switch nextErr.(type) {
case nil:
return e
case *rootError:
uErr := unpackRootErr(nextErr.(*rootError))
e.ErrRoot = uErr.ErrRoot
case *wrapError:
e = unpackWrapErr(chain, nextErr.(*wrapError))
default:
e.ExternalErr = err.Error()
}
return e
}

type ErrRoot struct {
Msg string
Stack []StackFrame
}

func (err *ErrRoot) formatStr(format Format) string {
if err == nil {
return ""
}
str := err.Msg
str += format.Msg
if format.WithTrace {
stackArr := formatStackFrames(err.Stack, format.TSep)
for _, frame := range stackArr {
str += format.TBeg
str += frame
str += format.Sep
}
}
return str
}

func (err *ErrRoot) formatJSON(format Format) map[string]interface{} {
if err == nil {
return nil
}
rootMap := make(map[string]interface{})
rootMap["message"] = fmt.Sprint(err.Msg)
if format.WithTrace {
rootMap["stack"] = formatStackFrames(err.Stack, format.TSep)
}
return rootMap
}

type ErrLink struct {
Msg string
Frame StackFrame
}

func (eLink *ErrLink) formatStr(format Format) string {
var str string
str += eLink.Msg
str += format.Msg
if format.WithTrace {
str += format.TBeg
str += eLink.Frame.formatFrame(format.TSep)
}
str += format.Sep
return str
}

func (eLink *ErrLink) formatJSON(format Format) map[string]interface{} {
wrapMap := make(map[string]interface{})
wrapMap["message"] = fmt.Sprint(eLink.Msg)
if format.WithTrace {
wrapMap["stack"] = eLink.Frame.formatFrame(format.TSep)
}
return wrapMap
}

func formatStackFrames(s []StackFrame, sep string) []string {
var str []string
for _, f := range s {
str = append(str, f.formatFrame(sep))
}
return str
}
Loading

0 comments on commit dfafd27

Please sign in to comment.