diff --git a/README.md b/README.md
index 90434e6..443e043 100644
--- a/README.md
+++ b/README.md
@@ -72,7 +72,7 @@ Expect(req).To(be_http.Request(
be_json.Matcher(
be_json.JsonAsReader,
be_json.HaveKeyValue("hello", "world"),
- be_json.HaveKeyValue("n", be_reflected.AsIntish()),
+ be_json.HaveKeyValue("n", be_reflected.AsInteger()),
be_json.HaveKeyValue("ids", be_reflected.AsSliceOf[string]),
be_json.HaveKeyValue("details", And(
be_reflected.AsObjects(),
@@ -121,7 +121,7 @@ types.
[See detailed docs](be_reflected/README.md)
#### Data Type Matchers based on reflect.Kind
-`AsString`, `AsBytes`, `AsNumeric`, `AsNumericString`, `AsIntish`, `AsIntishString`, `AsFloatish`, `AsFloatishString`,
+`AsString`, `AsBytes`, `AsNumeric`, `AsNumericString`, `AsInteger`, `AsIntegerString`, `AsFloat`, `AsFloatishString`,
#### Interface Matchers based on reflect.Kind
diff --git a/be_math/README.md b/be_math/README.md
index 1b50659..8331a4a 100644
--- a/be_math/README.md
+++ b/be_math/README.md
@@ -34,8 +34,7 @@ DivisibleBy succeeds if actual is numerically divisible by the passed-in value.
```go
func Even() types.BeMatcher
```
-Even succeeds if actual is an even numeric value. todo: test if failure message
-is OK
+Even succeeds if actual is an even numeric value.
#### func GreaterThan
@@ -128,8 +127,7 @@ Negative succeeds if actual is a negative numeric value.
```go
func Odd() types.BeMatcher
```
-Odd succeeds if actual is an odd numeric value. todo: test if failure message is
-OK
+Odd succeeds if actual is an odd numeric value.
#### func Positive
diff --git a/be_math/matchers_math.go b/be_math/matchers_math.go
index 4b7c7a0..3a85476 100644
--- a/be_math/matchers_math.go
+++ b/be_math/matchers_math.go
@@ -53,51 +53,62 @@ func InRange(from any, fromInclusive bool, until any, untilInclusive bool) types
} else {
group[1] = Lt(until)
}
- return psi_matchers.NewAllMatcher(cast.AsSliceOfAny(group)...)
+
+ // For compiling a nice failure message we better use `[from, until)` format
+ leftBracket, rightBracket := "(", ")"
+ if fromInclusive {
+ leftBracket = "["
+ }
+ if untilInclusive {
+ rightBracket = "]"
+ }
+
+ return WithCustomMessage(
+ psi_matchers.NewAllMatcher(cast.AsSliceOfAny(group)...),
+ fmt.Sprintf("be in range %s%v, %v%s", leftBracket, from, until, rightBracket),
+ )
}
// Odd succeeds if actual is an odd numeric value.
-// todo: test if failure message is OK
func Odd() types.BeMatcher {
- return Psi(
- be_reflected.AsIntish(),
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
+ be_reflected.AsInteger(),
WithFallibleTransform(func(actual any) any {
return int(cast.AsFloat(actual))%2 != 0
}, gomega.BeTrue()),
- )
+ ), "be an odd number")
}
// Even succeeds if actual is an even numeric value.
-// todo: test if failure message is OK
func Even() types.BeMatcher {
- return Psi(
- be_reflected.AsIntish(),
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
+ be_reflected.AsInteger(),
WithFallibleTransform(func(actual any) any {
return int(cast.AsFloat(actual))%2 == 0
}, gomega.BeTrue()),
- )
+ ), "be an even number")
}
// Negative succeeds if actual is a negative numeric value.
func Negative() types.BeMatcher {
- return Psi(gcustom.MakeMatcher(LessThan(0.0).Match, "be negative"))
+ return WithCustomMessage(LessThan(0.0), "be negative")
}
// Positive succeeds if actual is a positive numeric value.
func Positive() types.BeMatcher {
- return Psi(gcustom.MakeMatcher(GreaterThan(0.0).Match, "be positive"))
+ return WithCustomMessage(GreaterThan(0.0), "be positive")
}
// Zero succeeds if actual is numerically equal to zero.
// Any type of int/float will work for comparison.
func Zero() types.BeMatcher {
- return Psi(gcustom.MakeMatcher(Approx(0, 0).Match, "be zero"))
+ return WithCustomMessage(Approx(0, 0), "be zero")
}
// ApproxZero succeeds if actual is numerically approximately equal to zero
// Any type of int/float will work for comparison.
func ApproxZero() types.BeMatcher {
- return Psi(gcustom.MakeMatcher(Approx(0, 1e-8).Match, "be approximately zero"))
+ return WithCustomMessage(Approx(0, 1e-8), "be approximately zero")
}
// Integral succeeds if actual is an integral float, meaning it has zero decimal places.
diff --git a/be_math/matchers_math_test.go b/be_math/matchers_math_test.go
index 6f57ae8..50af90c 100644
--- a/be_math/matchers_math_test.go
+++ b/be_math/matchers_math_test.go
@@ -5,14 +5,20 @@ import (
"github.com/expectto/be/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
+ "strings"
)
var _ = Describe("BeMath", func() {
DescribeTable("should positively match", func(matcher types.BeMatcher, actual any) {
+ // check gomega-compatible matching:
success, err := matcher.Match(actual)
Expect(err).Should(Succeed())
Expect(success).To(BeTrue())
+
+ // check gomock-compatible matching:
+ success = matcher.Matches(actual)
+ Expect(success).To(BeTrue())
},
Entry("10 GreaterThan 5", be_math.GreaterThan(5), 10),
Entry("10 GreaterThan 5 (alias)", be_math.Gt(5), 10),
@@ -71,9 +77,14 @@ var _ = Describe("BeMath", func() {
)
DescribeTable("should negatively match", func(matcher types.BeMatcher, actual any) {
+ // check gomega-compatible matching:
success, err := matcher.Match(actual)
Expect(err).Should(Succeed())
Expect(success).To(BeFalse())
+
+ // check gomock-compatible matching:
+ success = matcher.Matches(actual)
+ Expect(success).To(BeFalse())
},
Entry("5 is not GreaterThan 10", be_math.GreaterThan(10), 5),
Entry("5 is not GreaterThan 5 (alias)", be_math.Gt(5), 5),
@@ -104,11 +115,13 @@ var _ = Describe("BeMath", func() {
Entry("4 is not an odd number", be_math.Odd(), 4),
Entry("-2 is not an odd number", be_math.Odd(), -2),
Entry("-4 is not an odd number", be_math.Odd(), -4),
+ Entry("floats can't be matched as odd numbers", be_math.Odd(), 1.5),
Entry("3 is not an even number", be_math.Even(), 3),
Entry("7 is not an even number", be_math.Even(), 7),
Entry("-3 is not an even number", be_math.Even(), -3),
Entry("-7 is not an even number", be_math.Even(), -7),
+ Entry("floats can't be matched as even numbers", be_math.Even(), 1.5),
Entry("5 is not a negative number", be_math.Negative(), 5),
Entry("8.5 is not a negative number", be_math.Negative(), 8.5),
@@ -127,11 +140,27 @@ var _ = Describe("BeMath", func() {
)
DescribeTable("should return a valid failure message", func(matcher types.BeMatcher, actual any, message string) {
- Expect(matcher.FailureMessage(actual)).To(Equal(message))
+ // FailureMessage is considered to be called after matching:
+ _, _ = matcher.Match(actual)
+
+ failureMessage := matcher.FailureMessage(actual)
+ Expect(failureMessage).To(Equal(message))
+
+ // in all our matchers negated failure messages are simply `to be` => `not to be`
+ Expect(matcher.NegatedFailureMessage(actual)).To(Equal(
+ strings.Replace(failureMessage, "\nto be ", "\nnot to be ", 1),
+ ))
},
+ // Example of entry where FailureMessage is simply inherited from gomega's underlying matching
Entry("5 is not GreaterThan 10", be_math.GreaterThan(10), 5, "Expected\n : 5\nto be >\n : 10"),
- Entry("10 is not divisible by 3", be_math.DivisibleBy(3), 10, "Expected:\n : 10\nto be divisible by 3"),
+ // Examples of entry with custom message (gcustom.MakeMatcher matching)
+ Entry("10 is not divisible by 3", be_math.DivisibleBy(3), 10, "Expected:\n : 10\nto be divisible by 3"),
Entry("0.1 is not zero", be_math.Zero(), 0.1, "Expected:\n : 0.1\nto be zero"),
+
+ // Examples of entry on complex Psi matchers (chaining + transform)
+ Entry("float is not odd", be_math.Odd(), 12.5, "Expected:\n : 12.5\nto be an odd number"),
+ Entry("8 is not odd", be_math.Odd(), 8, "Expected:\n : 8\nto be an odd number"),
+ Entry("8 (uint) is not odd", be_math.Odd(), uint(8), "Expected:\n : 8\nto be an odd number"),
)
})
diff --git a/be_reflected/README.md b/be_reflected/README.md
index c46dc44..98b2c0e 100644
--- a/be_reflected/README.md
+++ b/be_reflected/README.md
@@ -3,7 +3,10 @@
import "github.com/expectto/be/be_reflected"
Package be_reflected provides Be matchers that use reflection, enabling
-expressive assertions on values' reflect kinds and types.
+expressive assertions on values' reflect kinds and types. It consists of several
+"core" matchers e.g. AsKind / AssignableTo / Implementing And many other
+matchers that are made on-top on core ones. E.g. AsFunc / AsString / AsNumber /
+### etc
## Usage
@@ -29,20 +32,20 @@ func AsFinalPointer() types.BeMatcher
AsFinalPointer succeeds if the actual value is a final pointer, meaning it's a
pointer to a non-pointer type.
-#### func AsFloatish
+#### func AsFloat
```go
-func AsFloatish() types.BeMatcher
+func AsFloat() types.BeMatcher
```
-AsFloatish succeeds if actual is a numeric value that represents a
-floating-point value.
+AsFloat succeeds if actual is a numeric value that represents a floating-point
+value.
-#### func AsFloatishString
+#### func AsFloatString
```go
-func AsFloatishString() types.BeMatcher
+func AsFloatString() types.BeMatcher
```
-AsFloatishString succeeds if actual is a string that can be parsed into a valid
+AsFloatString succeeds if actual is a string that can be parsed into a valid
floating-point value.
#### func AsFunc
@@ -52,20 +55,20 @@ func AsFunc() types.BeMatcher
```
AsFunc succeeds if actual is of kind reflect.Func.
-#### func AsIntish
+#### func AsInteger
```go
-func AsIntish() types.BeMatcher
+func AsInteger() types.BeMatcher
```
-AsIntish succeeds if actual is a numeric value that represents an integer (from
+AsInteger succeeds if actual is a numeric value that represents an integer (from
reflect.Int up to reflect.Uint64).
-#### func AsIntishString
+#### func AsIntegerString
```go
-func AsIntishString() types.BeMatcher
+func AsIntegerString() types.BeMatcher
```
-AsIntishString succeeds if actual is a string that can be parsed into a valid
+AsIntegerString succeeds if actual is a string that can be parsed into a valid
integer value.
#### func AsKind
@@ -83,12 +86,12 @@ func AsMap() types.BeMatcher
```
AsMap succeeds if actual is of kind reflect.Map.
-#### func AsNumeric
+#### func AsNumber
```go
-func AsNumeric() types.BeMatcher
+func AsNumber() types.BeMatcher
```
-AsNumeric succeeds if actual is a numeric value, supporting various integer
+AsNumber succeeds if actual is a numeric value, supporting various integer
kinds: reflect.Int, ... reflect.Int64, and floating-point kinds:
reflect.Float32, reflect.Float64
diff --git a/be_reflected/matchers_reflected.go b/be_reflected/matchers_reflected.go
index 2015153..fada6c2 100644
--- a/be_reflected/matchers_reflected.go
+++ b/be_reflected/matchers_reflected.go
@@ -1,5 +1,7 @@
// Package be_reflected provides Be matchers that use reflection,
// enabling expressive assertions on values' reflect kinds and types.
+// It consists of several "core" matchers e.g. AsKind / AssignableTo / Implementing
+// And many other matchers that are made on-top on core ones. E.g. AsFunc / AsString / AsNumber / etc
package be_reflected
import (
@@ -7,6 +9,7 @@ import (
"github.com/expectto/be/internal/cast"
. "github.com/expectto/be/internal/psi"
"github.com/expectto/be/internal/psi_matchers"
+ reflect2 "github.com/expectto/be/internal/reflect"
"github.com/expectto/be/types"
"github.com/onsi/gomega"
"io"
@@ -23,42 +26,42 @@ func AssignableTo[T any]() types.BeMatcher { return psi_matchers.NewAssignableTo
// Implementing succeeds if actual implements the specified interface type T.
func Implementing[T any]() types.BeMatcher { return psi_matchers.NewImplementsMatcher[T]() }
+// Following matchers below are nice syntax-sugar, pretty usages of core matchers above:
+
// AsFunc succeeds if actual is of kind reflect.Func.
-func AsFunc() types.BeMatcher { return AsKind(reflect.Func) }
+func AsFunc() types.BeMatcher { return WithCustomMessage(AsKind(reflect.Func), "be a func") }
// AsChan succeeds if actual is of kind reflect.Chan.
-func AsChan() types.BeMatcher { return AsKind(reflect.Chan) }
+func AsChan() types.BeMatcher { return WithCustomMessage(AsKind(reflect.Chan), "be a channel") }
// AsPointer succeeds if the actual value is a pointer.
-func AsPointer() types.BeMatcher { return AsKind(reflect.Pointer) }
+func AsPointer() types.BeMatcher { return WithCustomMessage(AsKind(reflect.Pointer), "be a pointer") }
// AsFinalPointer succeeds if the actual value is a final pointer, meaning it's a pointer to a non-pointer type.
func AsFinalPointer() types.BeMatcher {
- return &psi_matchers.AllMatcher{Matchers: []types.BeMatcher{
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
AsPointer(),
WithFallibleTransform(func(actual any) any {
return reflect.ValueOf(actual).Elem()
}, psi_matchers.NewNotMatcher(AsPointer())),
- }}
+ ), "be a final pointer")
}
// AsStruct succeeds if actual is of kind reflect.Struct.
-func AsStruct() types.BeMatcher { return AsKind(reflect.Struct) }
+func AsStruct() types.BeMatcher { return WithCustomMessage(AsKind(reflect.Struct), "be a struct") }
// AsPointerToStruct succeeds if actual is a pointer to a struct.
func AsPointerToStruct() types.BeMatcher {
- return &psi_matchers.AllMatcher{Matchers: []types.BeMatcher{
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
AsPointer(),
WithFallibleTransform(func(actual any) any {
return reflect.ValueOf(actual).Elem()
}, AsStruct()),
- }}
+ ), "be a pointer to a struct")
}
// AsSlice succeeds if actual is of kind reflect.Slice.
-func AsSlice() types.BeMatcher {
- return AsKind(reflect.Slice)
-}
+func AsSlice() types.BeMatcher { return WithCustomMessage(AsKind(reflect.Slice), "be a slice") }
// AsPointerToSlice succeeds if actual is a pointer to a slice.
func AsPointerToSlice() types.BeMatcher {
@@ -73,114 +76,118 @@ func AsPointerToSlice() types.BeMatcher {
// AsSliceOf succeeds if actual is of kind reflect.Slice and each element of the slice
// is assignable to the specified type T.
func AsSliceOf[T any]() types.BeMatcher {
- return Psi(
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
AsKind(reflect.Slice),
gomega.HaveEach(AssignableTo[T]()),
- )
+ ), "be a slice of "+reflect2.TypeFor[T]().String())
}
// AsMap succeeds if actual is of kind reflect.Map.
-func AsMap() types.BeMatcher { return AsKind(reflect.Map) }
+func AsMap() types.BeMatcher { return WithCustomMessage(AsKind(reflect.Map), "be a map") }
// AsPointerToMap succeeds if actual is a pointer to a map.
func AsPointerToMap() types.BeMatcher {
- return &psi_matchers.AllMatcher{Matchers: []types.BeMatcher{
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
AsPointer(),
WithFallibleTransform(func(actual any) any {
return reflect.ValueOf(actual).Elem()
}, AsMap()),
- }}
+ ), "be a pointer to a map")
}
// AsObject is more specific than AsMap. It checks if the given `actual` value is a map with string keys
// and values of any type. This is particularly useful in the context of BeJson matcher,
// where the term 'Object' aligns with JSON notation.
func AsObject() types.BeMatcher {
- return &psi_matchers.AllMatcher{Matchers: []types.BeMatcher{
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
AsKind(reflect.Map), AssignableTo[map[string]any](),
- }}
+ ), "be an object")
}
func AsObjects() types.BeMatcher {
- return AsSliceOf[map[string]any]()
+ return WithCustomMessage(AsSliceOf[map[string]any](), "be objects")
}
// AsPointerToObject succeeds if actual is a pointer to a value that matches AsObject after applying dereference.
func AsPointerToObject() types.BeMatcher {
- return &psi_matchers.AllMatcher{Matchers: []types.BeMatcher{
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
AsPointer(),
WithFallibleTransform(func(actual any) any {
return reflect.ValueOf(actual).Elem()
}, AsObject()),
- }}
+ ), "be a pointer to an object")
}
// AsReader succeeds if actual implements the io.Reader interface.
-func AsReader() types.BeMatcher { return Implementing[io.Reader]() }
+func AsReader() types.BeMatcher {
+ return WithCustomMessage(Implementing[io.Reader](), "implement io.Reader interface")
+}
// AsStringer succeeds if actual implements the fmt.Stringer interface.
-func AsStringer() types.BeMatcher { return Implementing[fmt.Stringer]() }
+func AsStringer() types.BeMatcher {
+ return WithCustomMessage(Implementing[fmt.Stringer](), "implement fmt.Stringer interface")
+}
// AsString succeeds if actual is of kind reflect.String.
-func AsString() types.BeMatcher { return AsKind(reflect.String) }
+func AsString() types.BeMatcher { return WithCustomMessage(AsKind(reflect.String), "be a string") }
// AsBytes succeeds if actual is assignable to a slice of bytes ([]byte).
-func AsBytes() types.BeMatcher { return AssignableTo[[]byte]() }
+func AsBytes() types.BeMatcher { return WithCustomMessage(AssignableTo[[]byte](), "be bytes") }
-// AsNumeric succeeds if actual is a numeric value, supporting various
+// AsNumber succeeds if actual is a numeric value, supporting various
// integer kinds: reflect.Int, ... reflect.Int64,
// and floating-point kinds: reflect.Float32, reflect.Float64
-func AsNumeric() types.BeMatcher {
- return AsKind(
+func AsNumber() types.BeMatcher {
+ return WithCustomMessage(AsKind(
gomega.BeNumerically(">=", reflect.Int),
gomega.BeNumerically("<=", reflect.Float64),
- )
+ ), "be a number")
}
// AsNumericString succeeds if actual is a string that can be parsed into a valid numeric value.
func AsNumericString() types.BeMatcher {
- return &psi_matchers.AllMatcher{Matchers: []types.BeMatcher{
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
AsString(),
WithFallibleTransform(func(actual any) any {
_, err := strconv.ParseFloat(cast.AsString(actual), 64)
return err == nil
}, gomega.BeTrue()),
- }}
+ ), "be a numeric string")
}
-// AsIntish succeeds if actual is a numeric value that represents an integer (from reflect.Int up to reflect.Uint64).
-func AsIntish() types.BeMatcher {
- return AsKind(
+// AsInteger succeeds if actual is a numeric value that represents an integer (from reflect.Int up to reflect.Uint64).
+func AsInteger() types.BeMatcher {
+ return WithCustomMessage(AsKind(
gomega.BeNumerically(">=", reflect.Int),
gomega.BeNumerically("<=", reflect.Uint64),
- )
+ ), "be an integer value")
}
-// AsIntishString succeeds if actual is a string that can be parsed into a valid integer value.
-func AsIntishString() types.BeMatcher {
- return &psi_matchers.AllMatcher{Matchers: []types.BeMatcher{
+// AsIntegerString succeeds if actual is a string that can be parsed into a valid integer value.
+func AsIntegerString() types.BeMatcher {
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
AsString(),
WithFallibleTransform(func(actual any) any {
_, err := strconv.ParseInt(cast.AsString(actual), 10, 64)
return err == nil
}, gomega.BeTrue()),
- }}
+ ), "be an integer-ish string")
}
-// AsFloatish succeeds if actual is a numeric value that represents a floating-point value.
-func AsFloatish() types.BeMatcher {
- return AsKind(
+// AsFloat succeeds if actual is a numeric value that represents a floating-point value.
+func AsFloat() types.BeMatcher {
+ return WithCustomMessage(AsKind(
gomega.BeNumerically(">=", reflect.Float32),
gomega.BeNumerically("<=", reflect.Float64),
- )
+ ), "be a float value")
}
-// AsFloatishString succeeds if actual is a string that can be parsed into a valid floating-point value.
-func AsFloatishString() types.BeMatcher {
- return &psi_matchers.AllMatcher{Matchers: []types.BeMatcher{
+// AsFloatString succeeds if actual is a string that can be parsed into a valid floating-point value.
+func AsFloatString() types.BeMatcher {
+ return WithCustomMessage(psi_matchers.NewAllMatcher(
AsString(),
WithFallibleTransform(func(actual any) any {
_, err := strconv.ParseFloat(cast.AsString(actual), 64)
return err == nil
}, gomega.BeTrue()),
- }}
+ ), "be a float-ish string")
}
diff --git a/be_reflected/matchers_reflected_test.go b/be_reflected/matchers_reflected_test.go
index 94e34da..8949396 100644
--- a/be_reflected/matchers_reflected_test.go
+++ b/be_reflected/matchers_reflected_test.go
@@ -10,6 +10,8 @@ import (
"reflect"
)
+// TODO: unify tests. Let's make all tests like in `be_math`
+
var _ = Describe("MatchersReflected", func() {
Context("AsKind", func() {
DescribeTable("should match kind", func(actual interface{}, expected reflect.Kind) {
diff --git a/examples/examples_be_http_test.go b/examples/examples_be_http_test.go
index 858642d..7f4a094 100644
--- a/examples/examples_be_http_test.go
+++ b/examples/examples_be_http_test.go
@@ -80,8 +80,8 @@ var _ = Describe("matchers_http", func() {
be_json.Matcher(
be_json.JsonAsReader,
be_json.HaveKeyValue("hello", "world"),
- // TODO: AsIntish should work here, but it's not (as from payload via string, it's float)
- be_json.HaveKeyValue("n", be_reflected.AsFloatish()), // any int number
+ // TODO: AsInteger should work here, but it's not (as from payload via string, it's float)
+ be_json.HaveKeyValue("n", be_reflected.AsFloat()), // any int number
// TODO: fix me
//be_json.HaveKeyValue("ids", be_reflected.AsSliceOf[string]),
be_json.HaveKeyValue("details", And(
diff --git a/go.mod b/go.mod
index 89aa634..ee7ff79 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/expectto/be
-go 1.22
+go 1.21
require (
github.com/IGLOU-EU/go-wildcard v1.0.3 // latest
diff --git a/internal/psi/helpers.go b/internal/psi/helpers.go
index e5e810f..dddfc3c 100644
--- a/internal/psi/helpers.go
+++ b/internal/psi/helpers.go
@@ -3,6 +3,7 @@ package psi
import (
"github.com/expectto/be/types"
"github.com/onsi/gomega"
+ "github.com/onsi/gomega/gcustom"
)
// IsMatcher returns true if given input is either Omega or Gomock or a Psi matcher
@@ -28,3 +29,9 @@ func AsMatcher(m any) types.BeMatcher {
return FromGomega(gomega.Equal(t))
}
}
+
+// WithCustomMessage is a wrapper for gcustom.MakeMatcher
+// todo: make `v any` so we can check here if it's types.BeMatcher of match-func
+func WithCustomMessage(v types.BeMatcher, message string) types.BeMatcher {
+ return Psi(gcustom.MakeMatcher(v.Match, message))
+}
diff --git a/internal/psi_matchers/all_matcher.go b/internal/psi_matchers/all_matcher.go
index ce013c2..013a83f 100644
--- a/internal/psi_matchers/all_matcher.go
+++ b/internal/psi_matchers/all_matcher.go
@@ -66,3 +66,6 @@ func (m *AllMatcher) String() string {
}
// todo: AllMatcher.MatchMayChangeInTheFuture
+
+// todo: will be very nice if failure message will be slightly different
+// depending on which one matcher inside AndGroup fails