From a5330900a541b3a7e9e158de78a4bf9d30f68131 Mon Sep 17 00:00:00 2001 From: Hai Pham Date: Mon, 26 Mar 2018 01:43:15 +0800 Subject: [PATCH] feat(errorFuncF): added ErrorFunc and ErrorFuncF --- compose/constant_test.go | 4 ++-- compose/errorFunc.go | 10 ++++++++++ compose/errorFuncF.go | 27 +++++++++++++++++++++++++++ compose/function.go | 18 +++++++++++++----- compose/functionF.go | 22 +++++++++++----------- compose/functionF_test.go | 20 +++++++++++++++++--- compose/function_test.go | 15 +++++++++++++++ compose/noop.go | 6 +++--- compose/noop_test.go | 2 +- compose/publish.go | 8 ++++---- compose/publish_test.go | 8 ++++---- compose/retry.go | 34 +++++++++++++++++----------------- compose/retry_test.go | 32 ++++++++++++++++---------------- 13 files changed, 140 insertions(+), 66 deletions(-) create mode 100644 compose/errorFunc.go create mode 100644 compose/errorFuncF.go create mode 100644 compose/function_test.go diff --git a/compose/constant_test.go b/compose/constant_test.go index 27d2163..80837b2 100644 --- a/compose/constant_test.go +++ b/compose/constant_test.go @@ -6,8 +6,8 @@ import ( ) const ( - delayTime = time.Duration(1e8) - retryCount = uint(10) + delay = time.Duration(1e8) + retries = uint(10) // This value should be returned by test Functions. valueOp = 1 diff --git a/compose/errorFunc.go b/compose/errorFunc.go new file mode 100644 index 0000000..b97b36b --- /dev/null +++ b/compose/errorFunc.go @@ -0,0 +1,10 @@ +package compose + +// ErrorFunc represents an error-returning function. This is a specialized +// form of Func, and thus is constructible from a Func. +type ErrorFunc func() error + +// Invoke invokes the underlying error function. +func (ef ErrorFunc) Invoke() error { + return ef() +} diff --git a/compose/errorFuncF.go b/compose/errorFuncF.go new file mode 100644 index 0000000..cb77845 --- /dev/null +++ b/compose/errorFuncF.go @@ -0,0 +1,27 @@ +package compose + +// ErrorFuncF converts an ErrorFunc to another ErrorFunc. +type ErrorFuncF func(ErrorFunc) ErrorFunc + +// Wrap is a convenience method that calls the underlying ErrorFuncF. +func (eff ErrorFuncF) Wrap(ef ErrorFunc) ErrorFunc { + return eff(ef) +} + +// ErrorFuncF converts a FuncF to an ErrorFuncF. This method should be used at +// the last stage of a chain so we do not need to redefine other higher-order +// functions. +func (ff FuncF) ErrorFuncF() ErrorFuncF { + return func(ef ErrorFunc) ErrorFunc { + var function Func = func() (interface{}, error) { + return nil, ef() + } + + wrapped := ff.Wrap(function) + + return func() error { + _, err := wrapped.Invoke() + return err + } + } +} diff --git a/compose/function.go b/compose/function.go index c5d6f1d..dad1438 100644 --- a/compose/function.go +++ b/compose/function.go @@ -1,10 +1,18 @@ package compose -// Function represents an operation that could return an error. -type Function func() (interface{}, error) +// Func represents an operation that could return an error. +type Func func() (interface{}, error) -// Invoke is a convenience method to call a Function. Although it is the same -// as if we call the function normally, this may look nicer in a chain. -func (f Function) Invoke() (interface{}, error) { +// Invoke is a convenience method to call a Func. Although it is the same as if +// we call the function normally, this may look nicer in a chain. +func (f Func) Invoke() (interface{}, error) { return f() } + +// ErrorFunc converts the current function into an ErrorFunc. +func (f Func) ErrorFunc() ErrorFunc { + return func() error { + _, err := f.Invoke() + return err + } +} diff --git a/compose/functionF.go b/compose/functionF.go index 8a27877..32e9c7d 100644 --- a/compose/functionF.go +++ b/compose/functionF.go @@ -1,24 +1,24 @@ package compose -// FunctionF transforms a Function into another Function. -type FunctionF func(Function) Function +// FuncF transforms a Func into another Func. +type FuncF func(Func) Func -// Compose composes the functionalities of both FunctionF. We can use this to -// chain enhance a base Function without exposing implementation details. -func (ff FunctionF) Compose(selector FunctionF) FunctionF { - return func(f Function) Function { +// Compose composes the functionalities of both FuncF. We can use this to chain +// enhance a base Func without exposing implementation details. +func (ff FuncF) Compose(selector FuncF) FuncF { + return func(f Func) Func { return ff(selector(f)) } } // ComposeFn is similar to Compose, but it is more convenient when we deal with -// functions that return FunctionF. -func (ff FunctionF) ComposeFn(selectorFn func() FunctionF) FunctionF { +// functions that return FuncF. +func (ff FuncF) ComposeFn(selectorFn func() FuncF) FuncF { return ff.Compose(selectorFn()) } -// Wrap is a convenience method to invoke the wrap on a Function. This may look -// nice in a function chain. -func (ff FunctionF) Wrap(f Function) Function { +// Wrap is a convenience method to invoke the wrap on a Func. This may look +// nicer in a function chain. +func (ff FuncF) Wrap(f Func) Func { return ff(f) } diff --git a/compose/functionF_test.go b/compose/functionF_test.go index 601c2db..67938ce 100644 --- a/compose/functionF_test.go +++ b/compose/functionF_test.go @@ -7,7 +7,7 @@ import ( func TestCompose(t *testing.T) { published := 0 - retryF := RetryF(retryCount) + retryF := RetryF(retries) publishF := PublishF(func(value interface{}, err error) { published++ @@ -20,8 +20,8 @@ func TestCompose(t *testing.T) { /// When && Then 1 retryF.Compose(publishF).ComposeFn(NoopF)(errF)() - if uint(published) != retryCount+1 { - t.Errorf("Expected %d, got %d", retryCount+1, published) + if uint(published) != retries+1 { + t.Errorf("Expected %d, got %d", retries+1, published) } /// When && Then 2 @@ -33,6 +33,20 @@ func TestCompose(t *testing.T) { } } +func TestComposeConvertToErrorFuncF(t *testing.T) { + /// Setup + errF := func() error { + return errOp + } + + retryF := RetryF(retries).ErrorFuncF() + + /// When & Then + if err := retryF.Wrap(errF).Invoke(); err != errOp { + t.Errorf("Expected %v, got %v", errOp, err) + } +} + func BenchmarkComposition(b *testing.B) { errF := func() (interface{}, error) { return valueOp, errOp diff --git a/compose/function_test.go b/compose/function_test.go new file mode 100644 index 0000000..32d0362 --- /dev/null +++ b/compose/function_test.go @@ -0,0 +1,15 @@ +package compose + +import "testing" + +func TestConvertToErrorFunc(t *testing.T) { + /// Setup + var errF Func = func() (interface{}, error) { + return valueOp, errOp + } + + /// When & Then + if err := errF.ErrorFunc().Invoke(); err != errOp { + t.Errorf("Expected %v, got %v", errOp, err) + } +} diff --git a/compose/noop.go b/compose/noop.go index 917ce9e..2be6cf7 100644 --- a/compose/noop.go +++ b/compose/noop.go @@ -1,13 +1,13 @@ package compose // NoopF does nothing and simply returns the function. -func NoopF() FunctionF { - return func(f Function) Function { +func NoopF() FuncF { + return func(f Func) Func { return f } } // Noop is a convenience function that calls the composable NoopF under the hood. -func (f Function) Noop() Function { +func (f Func) Noop() Func { return NoopF().Wrap(f) } diff --git a/compose/noop_test.go b/compose/noop_test.go index 0fd4b87..b72fd33 100644 --- a/compose/noop_test.go +++ b/compose/noop_test.go @@ -4,7 +4,7 @@ import "testing" func TestNoop(t *testing.T) { /// Setup - var errF Function = func() (interface{}, error) { + var errF Func = func() (interface{}, error) { return valueOp, errOp } diff --git a/compose/publish.go b/compose/publish.go index 097a232..1712a48 100644 --- a/compose/publish.go +++ b/compose/publish.go @@ -1,8 +1,8 @@ package compose -// PublishF publishes the result of a Function for side effects. -func PublishF(callback func(interface{}, error)) FunctionF { - return func(f Function) Function { +// PublishF publishes the result of a Func for side effects. +func PublishF(callback func(interface{}, error)) FuncF { + return func(f Func) Func { return func() (interface{}, error) { value, err := f() callback(value, err) @@ -13,6 +13,6 @@ func PublishF(callback func(interface{}, error)) FunctionF { // Publish is a convenience function that calls the composable PublishF under // the hood. -func (f Function) Publish(callback func(interface{}, error)) Function { +func (f Func) Publish(callback func(interface{}, error)) Func { return PublishF(callback).Wrap(f) } diff --git a/compose/publish_test.go b/compose/publish_test.go index fe79e98..17fa5b7 100644 --- a/compose/publish_test.go +++ b/compose/publish_test.go @@ -10,7 +10,7 @@ func TestPublish(t *testing.T) { var publishedValue interface{} var publishedErr error - var errF Function = func() (interface{}, error) { + var errF Func = func() (interface{}, error) { return valueOp, errOp } @@ -21,7 +21,7 @@ func TestPublish(t *testing.T) { } /// When & Then - value, err := errF.Publish(publishF).Retry(retryCount).Invoke() + value, err := errF.Publish(publishF).Retry(retries).Invoke() if err != errOp || value != nil { t.Errorf("Expected %v, got %v", errOp, err) @@ -35,7 +35,7 @@ func TestPublish(t *testing.T) { t.Errorf("Expected %v, got %v", errOp, publishedErr) } - if uint(published) != retryCount+1 { - t.Errorf("Expected %v, got %v", retryCount+1, published) + if uint(published) != retries+1 { + t.Errorf("Expected %v, got %v", retries+1, published) } } diff --git a/compose/retry.go b/compose/retry.go index f1f912b..855871f 100644 --- a/compose/retry.go +++ b/compose/retry.go @@ -2,11 +2,11 @@ package compose import "time" -// CountRetryF composes a Function with retry capabilities. The error function -// has access to the current retry count in its first parameter, which is -// useful e.g when we are implementing a delay mechanism. -func CountRetryF(retryCount uint) func(func(uint) (interface{}, error)) Function { - return func(f func(uint) (interface{}, error)) Function { +// CountRetryF composes a Func with retry capabilities. The error function has +// access to the current retry count in its first parameter, which is useful +// e.g when we are implementing a delay mechanism. +func CountRetryF(retryCount uint) func(func(uint) (interface{}, error)) Func { + return func(f func(uint) (interface{}, error)) Func { var retryF func(uint) (interface{}, error) retryF = func(current uint) (interface{}, error) { @@ -31,24 +31,24 @@ func CountRetryF(retryCount uint) func(func(uint) (interface{}, error)) Function // RetryF has the same semantics as CountRetry, but ignores the current retry // count. -func RetryF(retryCount uint) FunctionF { - return func(f Function) Function { +func RetryF(retryCount uint) FuncF { + return func(f Func) Func { return CountRetryF(retryCount)(func(retry uint) (interface{}, error) { return f() }) } } -// Retry is a convenience method to chain Function, using the compose RetryF -// function under the hood. -func (f Function) Retry(retryCount uint) Function { +// Retry is a convenience method to chain Func, using the compose RetryF under +// the hood. +func (f Func) Retry(retryCount uint) Func { return RetryF(retryCount).Wrap(f) } // delayRetry composes a function with retry-delaying capabilities. The output // of the return function can be fed to a CountRetry composition. -func delayRetry(d time.Duration) func(Function) func(uint) (interface{}, error) { - return func(f Function) func(uint) (interface{}, error) { +func delayRetry(d time.Duration) func(Func) func(uint) (interface{}, error) { + return func(f Func) func(uint) (interface{}, error) { return func(retry uint) (interface{}, error) { if retry > 0 { time.Sleep(d) @@ -60,9 +60,9 @@ func delayRetry(d time.Duration) func(Function) func(uint) (interface{}, error) } // DelayRetryF composes retry with delay capabilities. -func DelayRetryF(retryCount uint) func(time.Duration) FunctionF { - return func(delay time.Duration) FunctionF { - return func(f Function) Function { +func DelayRetryF(retryCount uint) func(time.Duration) FuncF { + return func(delay time.Duration) FuncF { + return func(f Func) Func { return CountRetryF(retryCount)(delayRetry(delay)(f)) } } @@ -70,8 +70,8 @@ func DelayRetryF(retryCount uint) func(time.Duration) FunctionF { // DelayRetry is a convenience function that calls the composable DelayRetryF // function under the hood. -func (f Function) DelayRetry(retryCount uint) func(time.Duration) Function { - return func(d time.Duration) Function { +func (f Func) DelayRetry(retryCount uint) func(time.Duration) Func { + return func(d time.Duration) Func { return DelayRetryF(retryCount)(d).Wrap(f) } } diff --git a/compose/retry_test.go b/compose/retry_test.go index b1c22a9..8c0d24b 100644 --- a/compose/retry_test.go +++ b/compose/retry_test.go @@ -15,12 +15,12 @@ func TestCountRetryCompose(t *testing.T) { } /// When & Then - if _, err := CountRetryF(retryCount)(errF)(); err != errOp { + if _, err := CountRetryF(retries)(errF)(); err != errOp { t.Errorf("Expected %v, got %v", errOp, err) } - if currentRetry != retryCount { - t.Errorf("Expected %v, got %v", retryCount, currentRetry) + if currentRetry != retries { + t.Errorf("Expected %v, got %v", retries, currentRetry) } } @@ -41,7 +41,7 @@ func TestRetryComposeWithInitialError(t *testing.T) { } /// When & Then - if value, err := RetryF(retryCount)(errF)(); err != nil || value != valueOp { + if value, err := RetryF(retries)(errF)(); err != nil || value != valueOp { t.Errorf("Should not error, but got %v", err) } @@ -54,18 +54,18 @@ func TestRetryComposeWithAllErrors(t *testing.T) { /// Setup invoked := uint(0) - var errF Function = func() (interface{}, error) { + var errF Func = func() (interface{}, error) { invoked++ return valueOp, errOp } /// When & Then - if _, err := errF.Retry(retryCount).Invoke(); err != errOp { + if _, err := errF.Retry(retries).Invoke(); err != errOp { t.Errorf("Expected %v, got %v", errOp, err) } - if invoked != retryCount+1 { - t.Errorf("Expected %d, got %d", retryCount+1, invoked) + if invoked != retries+1 { + t.Errorf("Expected %d, got %d", retries+1, invoked) } } @@ -80,14 +80,14 @@ func TestDelayRetry(t *testing.T) { /// When & Then start := time.Now() - if _, err := delayRetry(delayTime)(errF)(currentRetry); err != errOp { + if _, err := delayRetry(delay)(errF)(currentRetry); err != errOp { t.Errorf("Expected %v, got %v", errOp, err) } difference := time.Now().Sub(start) - if difference < delayTime { - t.Errorf("Expected %d, got %d", delayTime, difference) + if difference < delay { + t.Errorf("Expected %d, got %d", delay, difference) } } @@ -100,33 +100,33 @@ func TestDelayRetryForFirstInvocation(t *testing.T) { /// When & Then start := time.Now() - if _, err := delayRetry(delayTime)(errF)(0); err != errOp { + if _, err := delayRetry(delay)(errF)(0); err != errOp { t.Errorf("Expected %v, got %v", errOp, err) } difference := time.Now().Sub(start) - if difference >= delayTime { + if difference >= delay { t.Errorf("Should not have delayed, but got %d", difference) } } func TestDelayedRetry(t *testing.T) { /// Setup - var errF Function = func() (interface{}, error) { + var errF Func = func() (interface{}, error) { return valueOp, errOp } /// When & Then start := time.Now() - if _, err := errF.DelayRetry(retryCount)(delayTime).Invoke(); err != errOp { + if _, err := errF.DelayRetry(retries)(delay).Invoke(); err != errOp { t.Errorf("Expected %v, got %v", errOp, err) } difference := time.Now().Sub(start) - if int64(difference) < int64(delayTime)*int64(retryCount) { + if int64(difference) < int64(delay)*int64(retries) { t.Errorf("Wrong delay duration %d", difference) } }