diff --git a/cbreaker/predicates.go b/cbreaker/predicates.go index 606cfcb9..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,210 +43,3 @@ func parseExpression(in string) (hpredicate, error) { return pr, nil } - -type toInt func(c *CircuitBreaker) int - -type toFloat64 func(c *CircuitBreaker) float64 - -func latencyAtQuantile(quantile float64) toInt { - 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() toFloat64 { - return func(c *CircuitBreaker) float64 { - return c.metrics.NetworkErrorRatio() - } -} - -func responseCodeRatio(startA, endA, startB, endB int) toFloat64 { - 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 toInt: - return intEQ(mapper, value) - case toFloat64: - return float64EQ(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 toInt: - return intLT(mapper, value) - case toFloat64: - return float64LT(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 toInt: - return intGT(mapper, value) - case toFloat64: - return float64GT(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 intEQ(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 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) - if !ok { - return nil, fmt.Errorf("expected int, got %T", val) - } - - return func(c *CircuitBreaker) bool { - return m(c) < value - }, nil -} - -func float64GT(m toFloat64, val any) (hpredicate, error) { - value, ok := val.(float64) - 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..3dfa4448 --- /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 int | float64] 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 +}