From 3cd9d6b68c0429ce28fe2caf90a8520bbbd59b82 Mon Sep 17 00:00:00 2001 From: donutloop Date: Tue, 20 Feb 2024 02:55:39 +0100 Subject: [PATCH] Add functional predicate (#171) Enable the execution of assertion functions in a functional manner. --- function/predicate.go | 34 +++++++++++++++++ function/predicate_test.go | 75 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 function/predicate.go create mode 100644 function/predicate_test.go diff --git a/function/predicate.go b/function/predicate.go new file mode 100644 index 0000000..0d1acf3 --- /dev/null +++ b/function/predicate.go @@ -0,0 +1,34 @@ +package function + +// And returns a composed predicate that represents the logical AND of a list of predicates. +// It evaluates to true only if all predicates evaluate to true for the given value. +func And[T any](predicates ...func(T) bool) func(T) bool { + return func(value T) bool { + for _, predicate := range predicates { + if !predicate(value) { + return false // Short-circuit on the first false predicate + } + } + return true // True if all predicates are true + } +} + +// Negate returns a predicate that represents the logical negation of this predicate. +func Negate[T any](predicate func(T) bool) func(T) bool { + return func(value T) bool { + return !predicate(value) + } +} + +// Or returns a composed predicate that represents the logical OR of a list of predicates. +// It evaluates to true if at least one of the predicates evaluates to true for the given value. +func Or[T any](predicates ...func(T) bool) func(T) bool { + return func(value T) bool { + for _, predicate := range predicates { + if predicate(value) { + return true // Short-circuit on the first true predicate + } + } + return false // False if all predicates are false + } +} diff --git a/function/predicate_test.go b/function/predicate_test.go new file mode 100644 index 0000000..6ceb6f6 --- /dev/null +++ b/function/predicate_test.go @@ -0,0 +1,75 @@ +package function + +import ( + "strings" + "testing" + + "github.com/duke-git/lancet/v2/internal" +) + +func TestPredicatesNegatePure(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestPredicatesNegatePure") + + // Define some simple predicates for demonstration + isUpperCase := func(s string) bool { + return strings.ToUpper(s) == s + } + isLowerCase := func(s string) bool { + return strings.ToLower(s) == s + } + isMixedCase := Negate(Or(isUpperCase, isLowerCase)) + + assert.ShouldBeFalse(isMixedCase("ABC")) + assert.ShouldBeTrue(isMixedCase("AbC")) +} + +func TestPredicatesOrPure(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestPredicatesOrPure") + + containsDigitOrSpecialChar := Or( + func(s string) bool { return strings.ContainsAny(s, "0123456789") }, + func(s string) bool { return strings.ContainsAny(s, "!@#$%") }, + ) + + assert.ShouldBeTrue(containsDigitOrSpecialChar("hello!")) + assert.ShouldBeFalse(containsDigitOrSpecialChar("hello")) +} + +func TestPredicatesAndPure(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestPredicatesAndPure") + + isNumericAndLength5 := And( + func(s string) bool { return strings.ContainsAny(s, "0123456789") }, + func(s string) bool { return len(s) == 5 }, + ) + + assert.ShouldBeTrue(isNumericAndLength5("12345")) + assert.ShouldBeFalse(isNumericAndLength5("1234")) + assert.ShouldBeFalse(isNumericAndLength5("abcde")) +} + +func TestPredicatesMix(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestPredicatesMix") + + a := Or( + func(s string) bool { return strings.ContainsAny(s, "0123456789") }, + func(s string) bool { return strings.ContainsAny(s, "!") }, + ) + + b := And( + func(s string) bool { return strings.ContainsAny(s, "hello") }, + func(s string) bool { return strings.ContainsAny(s, "!") }, + ) + + c := Negate(And(a, b)) + + assert.ShouldBeFalse(c("hello!")) +}