1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-17 11:12:28 +08:00

Merge branch 'main' into v2

This commit is contained in:
dudaodong
2024-02-21 10:21:42 +08:00
5 changed files with 85 additions and 20 deletions

View File

@@ -32,3 +32,16 @@ func Or[T any](predicates ...func(T) bool) func(T) bool {
return false // False if all predicates are false return false // False if all predicates are false
} }
} }
// Nor returns a composed predicate that represents the logical NOR of a list of predicates.
// It evaluates to true only if all predicates evaluate to false for the given value.
func Nor[T any](predicates ...func(T) bool) func(T) bool {
return func(value T) bool {
for _, predicate := range predicates {
if predicate(value) {
return false // If any predicate evaluates to true, the NOR result is false
}
}
return true // Only returns true if all predicates evaluate to false
}
}

View File

@@ -54,6 +54,26 @@ func TestPredicatesAndPure(t *testing.T) {
assert.ShouldBeFalse(isNumericAndLength5("abcde")) assert.ShouldBeFalse(isNumericAndLength5("abcde"))
} }
func TestPredicatesNorPure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesNorPure")
match := Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
assert.ShouldBeTrue(match("dbcdckkeee"))
match = Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
assert.ShouldBeFalse(match("0123456789"))
}
func TestPredicatesMix(t *testing.T) { func TestPredicatesMix(t *testing.T) {
t.Parallel() t.Parallel()
@@ -72,4 +92,7 @@ func TestPredicatesMix(t *testing.T) {
c := Negate(And(a, b)) c := Negate(And(a, b))
assert.ShouldBeFalse(c("hello!")) assert.ShouldBeFalse(c("hello!"))
c = Nor(a, b)
assert.ShouldBeFalse(c("hello!"))
} }

View File

@@ -18,14 +18,14 @@ const (
// DefaultRetryTimes times of retry // DefaultRetryTimes times of retry
DefaultRetryTimes = 5 DefaultRetryTimes = 5
// DefaultRetryDuration time duration of two retries // DefaultRetryDuration time duration of two retries
DefaultRetryDuration = time.Second * 3 DefaultRetryLinearInterval = time.Second * 3
) )
// RetryConfig is config for retry // RetryConfig is config for retry
type RetryConfig struct { type RetryConfig struct {
context context.Context context context.Context
retryTimes uint retryTimes uint
retryDuration time.Duration backoffStrategy BackoffStrategy
} }
// RetryFunc is function that retry executes // RetryFunc is function that retry executes
@@ -42,11 +42,17 @@ func RetryTimes(n uint) Option {
} }
} }
// RetryDuration set duration of retries. // RetryWithLinearBackoff set linear strategy backoff
// Play: https://go.dev/play/p/nk2XRmagfVF // todo: Add playground link
func RetryDuration(d time.Duration) Option { func RetryWithLinearBackoff(interval time.Duration) Option {
if interval <= 0 {
panic("programming error: retry interval should not be lower or equal to 0")
}
return func(rc *RetryConfig) { return func(rc *RetryConfig) {
rc.retryDuration = d rc.backoffStrategy = &linear{
interval: interval,
}
} }
} }
@@ -63,21 +69,26 @@ func Context(ctx context.Context) Option {
// Play: https://go.dev/play/p/nk2XRmagfVF // Play: https://go.dev/play/p/nk2XRmagfVF
func Retry(retryFunc RetryFunc, opts ...Option) error { func Retry(retryFunc RetryFunc, opts ...Option) error {
config := &RetryConfig{ config := &RetryConfig{
retryTimes: DefaultRetryTimes, retryTimes: DefaultRetryTimes,
retryDuration: DefaultRetryDuration, context: context.TODO(),
context: context.TODO(),
} }
for _, opt := range opts { for _, opt := range opts {
opt(config) opt(config)
} }
if config.backoffStrategy == nil {
config.backoffStrategy = &linear{
interval: DefaultRetryLinearInterval,
}
}
var i uint var i uint
for i < config.retryTimes { for i < config.retryTimes {
err := retryFunc() err := retryFunc()
if err != nil { if err != nil {
select { select {
case <-time.After(config.retryDuration): case <-time.After(config.backoffStrategy.CalculateInterval()):
case <-config.context.Done(): case <-config.context.Done():
return errors.New("retry is cancelled") return errors.New("retry is cancelled")
} }
@@ -93,3 +104,21 @@ func Retry(retryFunc RetryFunc, opts ...Option) error {
return fmt.Errorf("function %s run failed after %d times retry", funcName, i) return fmt.Errorf("function %s run failed after %d times retry", funcName, i)
} }
// BackoffStrategy is an interface that defines a method for calculating backoff intervals.
type BackoffStrategy interface {
// CalculateInterval returns the time.Duration after which the next retry attempt should be made.
CalculateInterval() time.Duration
}
// linear is a struct that implements the BackoffStrategy interface using a linear backoff strategy.
type linear struct {
// interval specifies the fixed duration to wait between retry attempts.
interval time.Duration
}
// CalculateInterval is the method implementation for the linear struct.
// It returns the fixed interval defined in the linear struct.
func (l *linear) CalculateInterval() time.Duration {
return l.interval
}

View File

@@ -20,7 +20,7 @@ func ExampleContext() {
} }
Retry(increaseNumber, Retry(increaseNumber,
RetryDuration(time.Microsecond*50), RetryWithLinearBackoff(time.Microsecond*50),
Context(ctx), Context(ctx),
) )
@@ -30,7 +30,7 @@ func ExampleContext() {
// 4 // 4
} }
func ExampleRetryDuration() { func ExampleRetryWithLinearBackoff() {
number := 0 number := 0
increaseNumber := func() error { increaseNumber := func() error {
number++ number++
@@ -40,7 +40,7 @@ func ExampleRetryDuration() {
return errors.New("error occurs") return errors.New("error occurs")
} }
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50)) err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
if err != nil { if err != nil {
return return
} }
@@ -81,7 +81,7 @@ func ExampleRetry() {
return errors.New("error occurs") return errors.New("error occurs")
} }
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50)) err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
if err != nil { if err != nil {
return return
} }

View File

@@ -20,7 +20,7 @@ func TestRetryFailed(t *testing.T) {
return errors.New("error occurs") return errors.New("error occurs")
} }
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50)) err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
assert.IsNotNil(err) assert.IsNotNil(err)
assert.Equal(DefaultRetryTimes, number) assert.Equal(DefaultRetryTimes, number)
@@ -40,7 +40,7 @@ func TestRetrySucceeded(t *testing.T) {
return errors.New("error occurs") return errors.New("error occurs")
} }
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50)) err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
assert.IsNil(err) assert.IsNil(err)
assert.Equal(DefaultRetryTimes, number) assert.Equal(DefaultRetryTimes, number)
@@ -57,7 +57,7 @@ func TestSetRetryTimes(t *testing.T) {
return errors.New("error occurs") return errors.New("error occurs")
} }
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50), RetryTimes(3)) err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50), RetryTimes(3))
assert.IsNotNil(err) assert.IsNotNil(err)
assert.Equal(3, number) assert.Equal(3, number)
@@ -79,7 +79,7 @@ func TestCancelRetry(t *testing.T) {
} }
err := Retry(increaseNumber, err := Retry(increaseNumber,
RetryDuration(time.Microsecond*50), RetryWithLinearBackoff(time.Microsecond*50),
Context(ctx), Context(ctx),
) )