diff --git a/gotry_generate/main.go b/gotry_generate/main.go index 571aaf5..805aa1e 100644 --- a/gotry_generate/main.go +++ b/gotry_generate/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "errors" "flag" "fmt" "io" @@ -25,8 +26,8 @@ type line struct { Types []string } -func parseDefinitions(defFileName string, outFileName string) (err try.TryErr) { - defer try.Annotate(&err, "error parsing definitions") +func parseDefinitions(defFileName string, outFileName string) (err error) { + defer try.Annotate("error parsing definitions", &err) defFile := try.File(os.Open(defFileName)) @@ -77,12 +78,12 @@ func parseDefinitions(defFileName string, outFileName string) (err try.TryErr) { return } -func parseLine(lineStr string, imports map[string]bool) (*line, try.TryErr) { +func parseLine(lineStr string, imports map[string]bool) (*line, error) { lineParts := strings.Split(lineStr, ";") lineData := line{} if len(lineParts) < 2 { - return nil, try.NewErr("missing type name") + return nil, errors.New("missing type name") } // Parse variable name @@ -91,7 +92,7 @@ func parseLine(lineStr string, imports map[string]bool) (*line, try.TryErr) { lineData.Name = match[1] lineData.WithSlice = match[2] != "" } else { - return nil, try.NewErr("invalid variable name") + return nil, errors.New("invalid variable name") } // Parse type name(s) @@ -102,7 +103,7 @@ func parseLine(lineStr string, imports map[string]bool) (*line, try.TryErr) { } } if len(lineData.Types) == 0 { - return nil, try.NewErr("invalid type name") + return nil, errors.New("invalid type name") } // Parse imports @@ -114,7 +115,7 @@ func parseLine(lineStr string, imports map[string]bool) (*line, try.TryErr) { return &lineData, nil } -func copyLibrary(targetDir string, noTests, noStds bool) (caught try.TryErr) { +func copyLibrary(targetDir string, noTests, noStds bool) (caught error) { defer try.Return(&caught) filesToCopy := []string{"try.go"} @@ -143,6 +144,14 @@ func copyLibrary(targetDir string, noTests, noStds bool) (caught try.TryErr) { try.Check(dest.Close()) } + // Add go-generate command + gencmd := "\n//go:generate go run code.thetadev.de/ThetaDev/gotry/gotry_generate " + + "-def types.csv -o .\n" + f := try.File( + os.OpenFile(filepath.Join(targetDir, "try.go"), os.O_APPEND|os.O_WRONLY, 0o644)) + try.Empty(f.WriteString(gencmd)) + try.Check(f.Close()) + // If not present, add definition file template definitionFile := filepath.Join(targetDir, "types.csv") @@ -161,7 +170,7 @@ func goFmt(goFile string) error { } func main() { - defer try.Catch(func(err try.TryErr) { + defer try.Catch(func(err error) { fmt.Println("Error: " + err.Error()) }) diff --git a/try/std_types_test.go b/try/std_types_test.go index fef7111..2441cb3 100644 --- a/try/std_types_test.go +++ b/try/std_types_test.go @@ -15,7 +15,7 @@ func TestStrHelper_noThrow(t *testing.T) { } func TestStrHelper_throw(t *testing.T) { - var err TryErr + var err error defer Return(&err) String(throw()) @@ -31,8 +31,8 @@ func TestStrStrHelper(t *testing.T) { } func Example_copyFile() { - copyFile := func(src, dst string) (err TryErr) { - defer Annotate(&err, fmt.Sprintf("copy %s %s", src, dst)) + copyFile := func(src, dst string) (err error) { + defer Returnf(&err, "copy %s %s", src, dst) // These helpers are as fast as Check() calls r := File(os.Open(src)) diff --git a/try/try.go b/try/try.go index 5cb12f7..326a789 100644 --- a/try/try.go +++ b/try/try.go @@ -9,118 +9,9 @@ https://github.com/lainio/err2 import ( "errors" "fmt" - "regexp" - "runtime" "runtime/debug" - "strings" ) -// TryErr is an interface for an error with extended stack info. -type TryErr interface { - Error() string - Unwrap() error - CallStack() []call - CallStackString() string - PrintCallStack() - GetData() *tryErrData - Annotate(msg string) -} - -// tryErrObj is an extended error struct. -type tryErrObj struct { - err error - annotations []string - callStack []call -} - -// tryErrData is exported error data. Can be converted to JSON. -type tryErrData struct { - Msg string `json:"msg"` - Annotations []string `json:"annotations"` - CallStack []call `json:"call_stack"` -} - -type call struct { - Function string `json:"fn"` - Line int `json:"l"` -} - -func newErr(err error) *tryErrObj { - terr := &tryErrObj{err: err, callStack: []call{}} - - pc, _, _, ok := runtime.Caller(0) - if !ok { - return terr - } - thisFnc := runtime.FuncForPC(pc) - if thisFnc == nil { - return terr - } - - rexp := regexp.MustCompile(`.+\/`) - thisPkg := rexp.FindString(thisFnc.Name()) - - for i := 1; ; i++ { - pc, f, l, ok := runtime.Caller(i) - - if !ok { - break - } - - fnc := runtime.FuncForPC(pc).Name() - - if strings.HasPrefix(fnc, thisPkg) && !strings.HasSuffix(f, "_test.go") { - continue - } - - terr.callStack = append(terr.callStack, call{Line: l, Function: fnc}) - } - - return terr -} - -func NewErr(msg string) *tryErrObj { - return newErr(errors.New(msg)) -} - -func (e *tryErrObj) Error() string { - msgs := e.annotations - msgs = append(msgs, e.err.Error()) - return strings.Join(msgs, ": ") -} - -func (e *tryErrObj) Unwrap() error { - return e.err -} - -func (e *tryErrObj) CallStack() []call { - return e.callStack -} - -func (e *tryErrObj) CallStackString() string { - res := fmt.Sprintf("ERROR: %s\n", e.err.Error()) - for _, c := range e.callStack { - res += fmt.Sprintf("%s:%d\n", c.Function, c.Line) - } - return res -} - -func (e *tryErrObj) PrintCallStack() { - fmt.Println(e.CallStackString()) -} - -func (e *tryErrObj) GetData() *tryErrData { - return &tryErrData{ - Msg: e.err.Error(), - CallStack: e.callStack, - Annotations: e.annotations, - } -} - -func (e *tryErrObj) Annotate(msg string) { - e.annotations = append(e.annotations, msg) -} - // Empty is a helper method to handle errors of func() (string, error) functions. func Empty(_ interface{}, err error) { Check(err) @@ -139,7 +30,7 @@ func Any(args ...interface{}) []interface{} { // {return err} on happy path. func Check(err error) { if err != nil { - panic(newErr(err)) + panic(err) } } @@ -154,7 +45,7 @@ func check(args []interface{}) { if !ok { panic("wrong signature") } - panic(newErr(err)) + panic(err) } } @@ -162,7 +53,7 @@ func check(args []interface{}) { // functions returning errors them self. For those functions that doesn't // return errors there is a Catch function. Note! The handler function f is // called only when err != nil. -func Handle(err *TryErr, f func()) { +func Handle(err *error, f func()) { // This and Catch are similar but we need to call recover() here because // how it works with defer. We cannot refactor these to use same function. @@ -178,25 +69,42 @@ func Handle(err *TryErr, f func()) { if *err != nil { f() } - case TryErr: + case error: // We or someone did transport this error thru panic. - *err = r.(TryErr) + *err = r.(error) f() default: panic(r) } } +// Returnf wraps an error. It's similar to fmt.Errorf, but it's called only if +// error != nil. +func Returnf(err *error, format string, args ...interface{}) { + // This and Handle are similar but we need to call recover here because how + // it works with defer. We cannot refactor these two to use same function. + + if r := recover(); r != nil { + e, ok := r.(error) + if !ok { + panic(r) // Not ours, carry on panicking + } + *err = fmt.Errorf(format+": %v", append(args, e)...) + } else if *err != nil { // if other handlers call recovery() we still.. + *err = fmt.Errorf(format+": %v", append(args, *err)...) + } +} + // Catch is a convenient helper to those functions that doesn't return errors. // Go's main function is a good example. Note! There can be only one deferred // Catch function per non error returning function. See Handle for more // information. -func Catch(f func(err TryErr)) { +func Catch(f func(err error)) { // This and Handle are similar but we need to call recover here because how // it works with defer. We cannot refactor these 2 to use same function. if r := recover(); r != nil { - e, ok := r.(TryErr) + e, ok := r.(error) if !ok { panic(r) } @@ -206,12 +114,12 @@ func Catch(f func(err TryErr)) { // CatchAll is a helper function to catch and write handlers for all errors and // all panics thrown in the current go routine. -func CatchAll(errorHandler func(err TryErr), panicHandler func(v interface{})) { +func CatchAll(errorHandler func(err error), panicHandler func(v interface{})) { // This and Handle are similar but we need to call recover here because how // it works with defer. We cannot refactor these 2 to use same function. if r := recover(); r != nil { - e, ok := r.(TryErr) + e, ok := r.(error) if ok { errorHandler(e) } else { @@ -223,12 +131,12 @@ func CatchAll(errorHandler func(err TryErr), panicHandler func(v interface{})) { // CatchTrace is a helper function to catch and handle all errors. It recovers a // panic as well and prints its call stack. This is preferred helper for go // workers on long running servers. -func CatchTrace(errorHandler func(err TryErr)) { +func CatchTrace(errorHandler func(err error)) { // This and Handle are similar but we need to call recover here because how // it works with defer. We cannot refactor these 2 to use same function. if r := recover(); r != nil { - e, ok := r.(TryErr) + e, ok := r.(error) if ok { errorHandler(e) } else { @@ -241,12 +149,12 @@ func CatchTrace(errorHandler func(err TryErr)) { // Return is same as Handle but it's for functions which don't wrap or annotate // their errors. If you want to annotate errors see Annotate for more // information. -func Return(err *TryErr) { +func Return(err *error) { // This and Handle are similar but we need to call recover here because how // it works with defer. We cannot refactor these two to use same function. if r := recover(); r != nil { - e, ok := r.(TryErr) + e, ok := r.(error) if !ok { panic(r) // Not ours, carry on panicking } @@ -257,18 +165,20 @@ func Return(err *TryErr) { // Annotate is for annotating an error. It's similar to Returnf but it takes only // two arguments: a prefix string and a pointer to error. It adds ": " between // the prefix and the error text automatically. -func Annotate(err *TryErr, msg string) { +func Annotate(prefix string, err *error) { // This and Handle are similar but we need to call recover here because how // it works with defer. We cannot refactor these two to use same function. if r := recover(); r != nil { - e, ok := r.(TryErr) + e, ok := r.(error) if !ok { panic(r) // Not ours, carry on panicking } *err = e - e.Annotate(msg) + format := prefix + ": " + e.Error() + *err = errors.New(format) } else if *err != nil { // if other handlers call recovery() we still.. - (*err).Annotate(msg) + format := prefix + ": " + (*err).Error() + *err = errors.New(format) } } diff --git a/try/try_test.go b/try/try_test.go index 882ad1c..19ba93a 100644 --- a/try/try_test.go +++ b/try/try_test.go @@ -8,7 +8,6 @@ https://github.com/lainio/err2 import ( "fmt" - "strings" "testing" ) @@ -65,7 +64,7 @@ func TestAny_noError(t *testing.T) { } func TestDefault_Error(t *testing.T) { - var err TryErr + var err error defer Return(&err) Any(throw()) @@ -74,7 +73,7 @@ func TestDefault_Error(t *testing.T) { } func TestAny_Error(t *testing.T) { - var err TryErr + var err error defer Handle(&err, func() {}) Any(throw()) @@ -83,7 +82,7 @@ func TestAny_Error(t *testing.T) { } func panickingHandle() { - var err TryErr + var err error defer Handle(&err, func() {}) Any(wrongSignature()) @@ -99,7 +98,7 @@ func TestPanickingCarryOn_Handle(t *testing.T) { } func panickingCatchAll() { - defer CatchAll(func(err TryErr) {}, func(v interface{}) {}) + defer CatchAll(func(err error) {}, func(v interface{}) {}) Any(wrongSignature()) } @@ -114,7 +113,7 @@ func TestPanickingCatchAll(t *testing.T) { } func panickingCatchTrace() { - defer CatchTrace(func(err TryErr) {}) + defer CatchTrace(func(err error) {}) Any(wrongSignature()) } @@ -129,7 +128,7 @@ func TestPanickingCatchTrace(t *testing.T) { } func panickingReturn() { - var err TryErr + var err error defer Return(&err) Any(wrongSignature()) @@ -145,7 +144,7 @@ func TestPanicking_Return(t *testing.T) { } func panickingCatch() { - defer Catch(func(err TryErr) {}) + defer Catch(func(err error) {}) Any(wrongSignature()) } @@ -160,7 +159,7 @@ func TestPanicking_Catch(t *testing.T) { } func TestCatch_Error(t *testing.T) { - defer Catch(func(err TryErr) { + defer Catch(func(err error) { // fmt.Printf("error and defer handling:%s\n", err) }) @@ -170,15 +169,15 @@ func TestCatch_Error(t *testing.T) { } func ExampleReturn() { - var err TryErr + var err error defer Return(&err) Any(noThrow()) // Output: } func ExampleAnnotate() { - annotated := func() (err TryErr) { - defer Annotate(&err, "annotated") + annotated := func() (err error) { + defer Annotate("annotated", &err) Any(throw()) return err } @@ -187,22 +186,33 @@ func ExampleAnnotate() { // Output: annotated: this is an ERROR } -func ExampleAnnotate_deferStack() { - annotated := func() (err TryErr) { - defer Annotate(&err, "annotated 2nd") - defer Annotate(&err, "annotated 1st") +func ExampleReturnf() { + annotated := func() (err error) { + defer Returnf(&err, "annotated: %s", "err2") Any(throw()) return err } err := annotated() fmt.Printf("%v", err) - // Output: annotated 1st: annotated 2nd: this is an ERROR + // Output: annotated: err2: this is an ERROR +} + +func ExampleAnnotate_deferStack() { + annotated := func() (err error) { + defer Annotate("annotated 2nd", &err) + defer Annotate("annotated 1st", &err) + Any(throw()) + return err + } + err := annotated() + fmt.Printf("%v", err) + // Output: annotated 2nd: annotated 1st: this is an ERROR } func ExampleHandle() { - doSomething := func(a, b int) (err TryErr) { + doSomething := func(a, b int) (err error) { defer Handle(&err, func() { - err.Annotate(fmt.Sprintf("error with (%d, %d)", a, b)) + err = fmt.Errorf("error with (%d, %d): %w", a, b, err) }) Any(throw()) return err @@ -268,44 +278,3 @@ func BenchmarkRecursionWithErrorCheck_NotRelated(b *testing.B) { } } } - -func TestNewTryErr(t *testing.T) { - tryErr := NewErr("I f*cked up") - callStack := tryErr.CallStackString() - - if !strings.HasPrefix(callStack, "ERROR: I f*cked up\n") { - fmt.Println("Call stack does not have prefix.\n" + callStack) - t.Fail() - } - - if strings.Count(callStack, "\n") != 4 { - fmt.Println("Call stack is not 4 lines long.\n" + callStack) - t.Fail() - } -} - -func TestGetData(t *testing.T) { - tryErr := NewErr("I f*cked up") - tryErr.Annotate("test1") - tryErr.Annotate("test2") - data := tryErr.GetData() - - if data.Msg != "I f*cked up" { - fmt.Println("wrong msg") - t.Fail() - } - - if data.Annotations[0] != "test1" { - fmt.Println("wrong annotation#0") - t.Fail() - } - if data.Annotations[1] != "test2" { - fmt.Println("wrong annotation#1") - t.Fail() - } - - if len(data.CallStack) != 3 { - fmt.Println("call stack length != 3, " + fmt.Sprint(len(data.CallStack))) - t.Fail() - } -}