From 4a438d28e5c9dc4ec7f0800463ecd68c9bec034e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 23 Jun 2026 03:17:02 +0200 Subject: [PATCH 1/3] refactor: use generics --- cbreaker/predicates.go | 79 ++++++++++++------------------------------ 1 file changed, 22 insertions(+), 57 deletions(-) diff --git a/cbreaker/predicates.go b/cbreaker/predicates.go index 606cfcb9..c7f8caf9 100644 --- a/cbreaker/predicates.go +++ b/cbreaker/predicates.go @@ -45,11 +45,9 @@ func parseExpression(in string) (hpredicate, error) { return pr, nil } -type toInt func(c *CircuitBreaker) int +type toType[T any] func(c *CircuitBreaker) T -type toFloat64 func(c *CircuitBreaker) float64 - -func latencyAtQuantile(quantile float64) toInt { +func latencyAtQuantile(quantile float64) toType[int] { return func(c *CircuitBreaker) int { h, err := c.metrics.LatencyHistogram() if err != nil { @@ -61,13 +59,13 @@ func latencyAtQuantile(quantile float64) toInt { } } -func networkErrorRatio() toFloat64 { +func networkErrorRatio() toType[float64] { return func(c *CircuitBreaker) float64 { return c.metrics.NetworkErrorRatio() } } -func responseCodeRatio(startA, endA, startB, endB int) toFloat64 { +func responseCodeRatio(startA, endA, startB, endB int) toType[float64] { return func(c *CircuitBreaker) float64 { return c.metrics.ResponseCodeRatio(startA, endA, startB, endB) } @@ -109,10 +107,10 @@ func not(p hpredicate) hpredicate { // eq returns predicate that tests for equality of the value of the mapper and the constant. func eq(m any, value any) (hpredicate, error) { switch mapper := m.(type) { - case toInt: - return intEQ(mapper, value) - case toFloat64: - return float64EQ(mapper, value) + case toType[int]: + return genericEQ(mapper, value) + case toType[float64]: + return genericEQ(mapper, value) } return nil, fmt.Errorf("eq: unsupported argument: %T", m) @@ -131,10 +129,10 @@ func neq(m any, value any) (hpredicate, error) { // lt returns predicate that tests that value of the mapper function is less than the constant. func lt(m any, value any) (hpredicate, error) { switch mapper := m.(type) { - case toInt: - return intLT(mapper, value) - case toFloat64: - return float64LT(mapper, value) + case toType[int]: + return genericLT(mapper, value) + case toType[float64]: + return genericLT(mapper, value) } return nil, fmt.Errorf("lt: unsupported argument: %T", m) @@ -160,10 +158,10 @@ func le(m any, value any) (hpredicate, error) { // gt returns predicate that tests that value of the mapper function is greater than the constant. func gt(m any, value any) (hpredicate, error) { switch mapper := m.(type) { - case toInt: - return intGT(mapper, value) - case toFloat64: - return float64GT(mapper, value) + case toType[int]: + return genericGT(mapper, value) + case toType[float64]: + return genericGT(mapper, value) } return nil, fmt.Errorf("gt: unsupported argument: %T", m) @@ -186,8 +184,8 @@ func ge(m any, value any) (hpredicate, error) { }, nil } -func intEQ(m toInt, val any) (hpredicate, error) { - value, ok := val.(int) +func genericEQ[T int | float64](m toType[T], val any) (hpredicate, error) { + value, ok := val.(T) if !ok { return nil, fmt.Errorf("expected int, got %T", val) } @@ -197,41 +195,8 @@ func intEQ(m toInt, val any) (hpredicate, error) { }, nil } -func float64EQ(m toFloat64, val any) (hpredicate, error) { - value, ok := val.(float64) - if !ok { - return nil, fmt.Errorf("expected float64, got %T", val) - } - - return func(c *CircuitBreaker) bool { - return m(c) == value - }, nil -} - -func intLT(m toInt, val any) (hpredicate, error) { - value, ok := val.(int) - if !ok { - return nil, fmt.Errorf("expected int, got %T", val) - } - - return func(c *CircuitBreaker) bool { - return m(c) < value - }, nil -} - -func intGT(m toInt, val any) (hpredicate, error) { - value, ok := val.(int) - if !ok { - return nil, fmt.Errorf("expected int, got %T", val) - } - - return func(c *CircuitBreaker) bool { - return m(c) > value - }, nil -} - -func float64LT(m toFloat64, val any) (hpredicate, error) { - value, ok := val.(float64) +func genericLT[T int | float64](m toType[T], val any) (hpredicate, error) { + value, ok := val.(T) if !ok { return nil, fmt.Errorf("expected int, got %T", val) } @@ -241,8 +206,8 @@ func float64LT(m toFloat64, val any) (hpredicate, error) { }, nil } -func float64GT(m toFloat64, val any) (hpredicate, error) { - value, ok := val.(float64) +func genericGT[T int | float64](m toType[T], val any) (hpredicate, error) { + value, ok := val.(T) if !ok { return nil, fmt.Errorf("expected int, got %T", val) } From a57561aa4cace4ec653e5c24d1a11b0e8de3b9bb Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 23 Jun 2026 03:15:40 +0200 Subject: [PATCH 2/3] refactor: split predicate.go file --- cbreaker/predicates.go | 173 ------------------------------- cbreaker/predicates_functions.go | 31 ++++++ cbreaker/predicates_operators.go | 151 +++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 173 deletions(-) create mode 100644 cbreaker/predicates_functions.go create mode 100644 cbreaker/predicates_operators.go diff --git a/cbreaker/predicates.go b/cbreaker/predicates.go index c7f8caf9..dd368fb1 100644 --- a/cbreaker/predicates.go +++ b/cbreaker/predicates.go @@ -3,7 +3,6 @@ package cbreaker import ( "fmt" - "github.com/vulcand/oxy/v2/internal/holsterv4/clock" "github.com/vulcand/predicate" ) @@ -44,175 +43,3 @@ func parseExpression(in string) (hpredicate, error) { return pr, nil } - -type toType[T any] func(c *CircuitBreaker) T - -func latencyAtQuantile(quantile float64) toType[int] { - return func(c *CircuitBreaker) int { - h, err := c.metrics.LatencyHistogram() - if err != nil { - c.log.Error("Failed to get latency histogram, for %v error: %v", c, err) - return 0 - } - - return int(h.LatencyAtQuantile(quantile) / clock.Millisecond) - } -} - -func networkErrorRatio() toType[float64] { - return func(c *CircuitBreaker) float64 { - return c.metrics.NetworkErrorRatio() - } -} - -func responseCodeRatio(startA, endA, startB, endB int) toType[float64] { - return func(c *CircuitBreaker) float64 { - return c.metrics.ResponseCodeRatio(startA, endA, startB, endB) - } -} - -// or returns predicate by joining the passed predicates with logical 'or'. -func or(fns ...hpredicate) hpredicate { - return func(c *CircuitBreaker) bool { - for _, fn := range fns { - if fn(c) { - return true - } - } - - return false - } -} - -// and returns predicate by joining the passed predicates with logical 'and'. -func and(fns ...hpredicate) hpredicate { - return func(c *CircuitBreaker) bool { - for _, fn := range fns { - if !fn(c) { - return false - } - } - - return true - } -} - -// not creates negation of the passed predicate. -func not(p hpredicate) hpredicate { - return func(c *CircuitBreaker) bool { - return !p(c) - } -} - -// eq returns predicate that tests for equality of the value of the mapper and the constant. -func eq(m any, value any) (hpredicate, error) { - switch mapper := m.(type) { - case toType[int]: - return genericEQ(mapper, value) - case toType[float64]: - return genericEQ(mapper, value) - } - - return nil, fmt.Errorf("eq: unsupported argument: %T", m) -} - -// neq returns predicate that tests for inequality of the value of the mapper and the constant. -func neq(m any, value any) (hpredicate, error) { - p, err := eq(m, value) - if err != nil { - return nil, err - } - - return not(p), nil -} - -// lt returns predicate that tests that value of the mapper function is less than the constant. -func lt(m any, value any) (hpredicate, error) { - switch mapper := m.(type) { - case toType[int]: - return genericLT(mapper, value) - case toType[float64]: - return genericLT(mapper, value) - } - - return nil, fmt.Errorf("lt: unsupported argument: %T", m) -} - -// le returns predicate that tests that value of the mapper function is less or equal than the constant. -func le(m any, value any) (hpredicate, error) { - l, err := lt(m, value) - if err != nil { - return nil, err - } - - e, err := eq(m, value) - if err != nil { - return nil, err - } - - return func(c *CircuitBreaker) bool { - return l(c) || e(c) - }, nil -} - -// gt returns predicate that tests that value of the mapper function is greater than the constant. -func gt(m any, value any) (hpredicate, error) { - switch mapper := m.(type) { - case toType[int]: - return genericGT(mapper, value) - case toType[float64]: - return genericGT(mapper, value) - } - - return nil, fmt.Errorf("gt: unsupported argument: %T", m) -} - -// ge returns predicate that tests that value of the mapper function is less or equal than the constant. -func ge(m any, value any) (hpredicate, error) { - g, err := gt(m, value) - if err != nil { - return nil, err - } - - e, err := eq(m, value) - if err != nil { - return nil, err - } - - return func(c *CircuitBreaker) bool { - return g(c) || e(c) - }, nil -} - -func genericEQ[T int | float64](m toType[T], val any) (hpredicate, error) { - value, ok := val.(T) - if !ok { - return nil, fmt.Errorf("expected int, got %T", val) - } - - return func(c *CircuitBreaker) bool { - return m(c) == value - }, nil -} - -func genericLT[T int | float64](m toType[T], val any) (hpredicate, error) { - value, ok := val.(T) - if !ok { - return nil, fmt.Errorf("expected int, got %T", val) - } - - return func(c *CircuitBreaker) bool { - return m(c) < value - }, nil -} - -func genericGT[T int | float64](m toType[T], val any) (hpredicate, error) { - value, ok := val.(T) - if !ok { - return nil, fmt.Errorf("expected int, got %T", val) - } - - return func(c *CircuitBreaker) bool { - return m(c) > value - }, nil -} diff --git a/cbreaker/predicates_functions.go b/cbreaker/predicates_functions.go new file mode 100644 index 00000000..0f3b2617 --- /dev/null +++ b/cbreaker/predicates_functions.go @@ -0,0 +1,31 @@ +package cbreaker + +import ( + "github.com/vulcand/oxy/v2/internal/holsterv4/clock" +) + +type toType[T any] func(c *CircuitBreaker) T + +func latencyAtQuantile(quantile float64) toType[int] { + return func(c *CircuitBreaker) int { + h, err := c.metrics.LatencyHistogram() + if err != nil { + c.log.Error("Failed to get latency histogram, for %v error: %v", c, err) + return 0 + } + + return int(h.LatencyAtQuantile(quantile) / clock.Millisecond) + } +} + +func networkErrorRatio() toType[float64] { + return func(c *CircuitBreaker) float64 { + return c.metrics.NetworkErrorRatio() + } +} + +func responseCodeRatio(startA, endA, startB, endB int) toType[float64] { + return func(c *CircuitBreaker) float64 { + return c.metrics.ResponseCodeRatio(startA, endA, startB, endB) + } +} diff --git a/cbreaker/predicates_operators.go b/cbreaker/predicates_operators.go new file mode 100644 index 00000000..d783ec9e --- /dev/null +++ b/cbreaker/predicates_operators.go @@ -0,0 +1,151 @@ +package cbreaker + +import ( + "fmt" +) + +// or returns predicate by joining the passed predicates with logical 'or'. +func or(fns ...hpredicate) hpredicate { + return func(c *CircuitBreaker) bool { + for _, fn := range fns { + if fn(c) { + return true + } + } + + return false + } +} + +// and returns predicate by joining the passed predicates with logical 'and'. +func and(fns ...hpredicate) hpredicate { + return func(c *CircuitBreaker) bool { + for _, fn := range fns { + if !fn(c) { + return false + } + } + + return true + } +} + +// not creates negation of the passed predicate. +func not(p hpredicate) hpredicate { + return func(c *CircuitBreaker) bool { + return !p(c) + } +} + +// eq returns predicate that tests for equality of the value of the mapper and the constant. +func eq(m any, value any) (hpredicate, error) { + switch mapper := m.(type) { + case toType[int]: + return genericEQ(mapper, value) + case toType[float64]: + return genericEQ(mapper, value) + } + + return nil, fmt.Errorf("eq: unsupported argument: %T", m) +} + +// neq returns predicate that tests for inequality of the value of the mapper and the constant. +func neq(m any, value any) (hpredicate, error) { + p, err := eq(m, value) + if err != nil { + return nil, err + } + + return not(p), nil +} + +// lt returns predicate that tests that value of the mapper function is less than the constant. +func lt(m any, value any) (hpredicate, error) { + switch mapper := m.(type) { + case toType[int]: + return genericLT(mapper, value) + case toType[float64]: + return genericLT(mapper, value) + } + + return nil, fmt.Errorf("lt: unsupported argument: %T", m) +} + +// le returns predicate that tests that value of the mapper function is less or equal than the constant. +func le(m any, value any) (hpredicate, error) { + l, err := lt(m, value) + if err != nil { + return nil, err + } + + e, err := eq(m, value) + if err != nil { + return nil, err + } + + return func(c *CircuitBreaker) bool { + return l(c) || e(c) + }, nil +} + +// gt returns predicate that tests that value of the mapper function is greater than the constant. +func gt(m any, value any) (hpredicate, error) { + switch mapper := m.(type) { + case toType[int]: + return genericGT(mapper, value) + case toType[float64]: + return genericGT(mapper, value) + } + + return nil, fmt.Errorf("gt: unsupported argument: %T", m) +} + +// ge returns predicate that tests that value of the mapper function is less or equal than the constant. +func ge(m any, value any) (hpredicate, error) { + g, err := gt(m, value) + if err != nil { + return nil, err + } + + e, err := eq(m, value) + if err != nil { + return nil, err + } + + return func(c *CircuitBreaker) bool { + return g(c) || e(c) + }, nil +} + +func genericEQ[T int | float64](m toType[T], val any) (hpredicate, error) { + value, ok := val.(T) + if !ok { + return nil, fmt.Errorf("expected int, got %T", val) + } + + return func(c *CircuitBreaker) bool { + return m(c) == value + }, nil +} + +func genericLT[T int | float64](m toType[T], val any) (hpredicate, error) { + value, ok := val.(T) + if !ok { + return nil, fmt.Errorf("expected int, got %T", val) + } + + return func(c *CircuitBreaker) bool { + return m(c) < value + }, nil +} + +func genericGT[T int | float64](m toType[T], val any) (hpredicate, error) { + value, ok := val.(T) + if !ok { + return nil, fmt.Errorf("expected int, got %T", val) + } + + return func(c *CircuitBreaker) bool { + return m(c) > value + }, nil +} From ad1eb3df16196ecad7b38ae891041d7afd19e220 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 23 Jun 2026 03:34:18 +0200 Subject: [PATCH 3/3] refactor: restrict toType type --- cbreaker/predicates_functions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbreaker/predicates_functions.go b/cbreaker/predicates_functions.go index 0f3b2617..3dfa4448 100644 --- a/cbreaker/predicates_functions.go +++ b/cbreaker/predicates_functions.go @@ -4,7 +4,7 @@ import ( "github.com/vulcand/oxy/v2/internal/holsterv4/clock" ) -type toType[T any] func(c *CircuitBreaker) T +type toType[T int | float64] func(c *CircuitBreaker) T func latencyAtQuantile(quantile float64) toType[int] { return func(c *CircuitBreaker) int {