1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-12 16:52:29 +08:00

feat: add try-catch-finally implementation

This commit is contained in:
dudaodong
2024-11-04 14:46:25 +08:00
parent 840ea8f3c5
commit 8bbae69175
7 changed files with 475 additions and 70 deletions

100
xerror/trycatch.go Normal file
View File

@@ -0,0 +1,100 @@
package xerror
import (
"context"
"fmt"
"runtime"
)
// TryCatch is a struct to handle try-catch-finally block.
// This implementation is merely a simulation of Java-style try-catch and does not align with Go's error-handling philosophy. It is recommended to use it with caution.
type TryCatch struct {
ctx context.Context
tryFunc func(ctx context.Context) error
catchFunc func(ctx context.Context, err error)
finallyFunc func(ctx context.Context)
}
// NewTryCatch creates a new TryCatch instance.
func NewTryCatch(ctx context.Context) *TryCatch {
return &TryCatch{
ctx: ctx,
}
}
// Try sets the try function.
func (tc *TryCatch) Try(tryFunc func(ctx context.Context) error) *TryCatch {
tc.tryFunc = tryFunc
return tc
}
// Catch sets the catch function.
func (tc *TryCatch) Catch(catchFunc func(ctx context.Context, err error)) *TryCatch {
tc.catchFunc = catchFunc
return tc
}
// Finally sets the finally function.
func (tc *TryCatch) Finally(finallyFunc func(ctx context.Context)) *TryCatch {
tc.finallyFunc = finallyFunc
return tc
}
// Do executes the try-catch-finally block.
func (tc *TryCatch) Do() {
defer func() {
if r := recover(); r != nil {
if tc.catchFunc != nil {
err := fmt.Errorf("panic: %v", r)
tc.catchFunc(tc.ctx, WrapCatchError(err, "Recovered from panic"))
}
}
if tc.finallyFunc != nil {
tc.finallyFunc(tc.ctx)
}
}()
if tc.ctx.Err() != nil {
if tc.catchFunc != nil {
tc.catchFunc(tc.ctx, WrapCatchError(tc.ctx.Err(), "Context cancelled or timed out"))
}
return
}
if tc.tryFunc != nil {
if err := tc.tryFunc(tc.ctx); err != nil {
if tc.catchFunc != nil {
tc.catchFunc(tc.ctx, WrapCatchError(err, "Error in try block"))
}
}
}
}
// CatchError is an error type to handle try-catch-finally block.
type CatchError struct {
Msg string
File string
Line int
Cause error
}
// Error returns the error message.
func (e *CatchError) Error() string {
return fmt.Sprintf("%s at %s:%d - Cause: %v", e.Msg, e.File, e.Line, e.Cause)
}
// WrapCatchError wraps an error with message, file, and line.
func WrapCatchError(err error, msg string) *CatchError {
_, file, line, ok := runtime.Caller(2)
if !ok {
file = "unknown"
line = 0
}
return &CatchError{
Msg: msg,
File: file,
Line: line,
Cause: err,
}
}

172
xerror/trycatch_test.go Normal file
View File

@@ -0,0 +1,172 @@
package xerror
import (
"context"
"errors"
"testing"
"github.com/duke-git/lancet/v2/internal"
)
func TestTryCatchSuccess(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchSuccess")
counter := 0
calledCatch := false
calledFinally := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
counter++
return nil
}).Catch(func(ctx context.Context, err error) {
calledCatch = true
t.Errorf("Catch should not be called")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal(1, counter)
assert.Equal(false, calledCatch)
assert.Equal(true, calledFinally)
}
func TestTryCatchError(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchError")
var catchedError error
calledFinally := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
return errors.New("error")
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchPanic(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchPanic")
var catchedError error
calledFinally := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
panic("panic info")
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchContextCancelled(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchContextCancelled")
var catchedError error
calledFinally := false
ctx, cancel := context.WithCancel(context.Background())
cancel()
tc := NewTryCatch(ctx)
tc.Try(func(ctx context.Context) error {
return nil
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchContextTimeout(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchContextTimeout")
var catchedError error
calledFinally := false
ctx, cancel := context.WithTimeout(context.Background(), 0)
defer cancel()
tc := NewTryCatch(ctx)
tc.Try(func(ctx context.Context) error {
return nil
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchContextError(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchContextError")
var catchedError error
calledFinally := false
ctx, cancel := context.WithTimeout(context.Background(), 0)
defer cancel()
tc := NewTryCatch(ctx)
tc.Try(func(ctx context.Context) error {
return errors.New("error")
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchNoCatch(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchNoCatch")
calledFinally := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
return errors.New("error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal(true, calledFinally)
}

View File

@@ -1,6 +1,7 @@
package xerror
import (
"context"
"errors"
"fmt"
"reflect"
@@ -61,7 +62,7 @@ func ExampleXError_StackTrace() {
// Output:
// github.com/duke-git/lancet/v2/xerror.ExampleXError_StackTrace
// 52
// 53
// true
}
@@ -154,3 +155,27 @@ func ExampleTryUnwrap() {
// 42
// true
}
func ExampleTryCatch() {
calledFinally := false
calledCatch := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
return errors.New("error message")
}).Catch(func(ctx context.Context, err error) {
calledCatch = true
// Error in try block at /path/xxx.go:174 - Cause: error message
// fmt.Println(err.Error())
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
fmt.Println(calledCatch)
fmt.Println(calledFinally)
// Output:
// true
// true
}