-
Notifications
You must be signed in to change notification settings - Fork 3
/
matcher.go
191 lines (164 loc) · 5.91 KB
/
matcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package collections
import (
"reflect"
"github.com/thefuga/go-collections/internal"
)
type Matcher[K any, V any] func(key K, value V) bool
// AnyMatcher is used by matchers on functions that must compare keys and values from
// a collection.
// It is used as a functional option. To learn more, see: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
type AnyMatcher = Matcher[any, any]
// KeyEquals builds a matcher to compare the given key to the key passed by the matcher caller.
func KeyEquals(key any) AnyMatcher {
return func(collectionKey any, _ any) bool {
return key == collectionKey
}
}
// ValueDeepEquals builds a matcher to compare the given value (with reflect.DeepEqual)
// to the value passed by the matcher caller.
func ValueDeepEquals[K any, V any](value V) Matcher[K, V] {
return func(_ K, collectionValue V) bool {
return reflect.DeepEqual(value, collectionValue)
}
}
// ValueEquals builds a matcher to compare the given value (with ==)
// to the value passed by the matcher caller.
func ValueEquals[K any, V comparable](value V) Matcher[K, V] {
return func(_ K, collectionValue V) bool {
return value == collectionValue
}
}
// ValueDiffers builds a matcher to compare the given value (with reflect.DeepEqual)
// to the value passed by the matcher caller. It has the opposite behavior from ValueEquals
func ValueDiffers(value any) AnyMatcher {
return func(_ any, collectionValue any) bool {
return !reflect.DeepEqual(value, collectionValue)
}
}
// ValueGT compares the given numeric value to check if it is greater than the value
// given by the matcher's caller.
func ValueGT[K any, V internal.Relational](value V) Matcher[K, V] {
return func(_ K, collectionValue V) bool {
return collectionValue > value
}
}
// ValueCastGT acts just like ValueGT, but using AnyMatcher and performing a cast on the collection value.
func ValueCastGT[T internal.Number](value T) AnyMatcher {
return func(_ any, collectionValue any) bool {
if cast, ok := collectionValue.(T); ok {
return value < cast
}
return false
}
}
// ValueLT compares the given numeric value to check if it is lesser than the value
// given by the matcher's caller.
func ValueLT[K any, V internal.Relational](value V) Matcher[K, V] {
return func(_ K, collectionValue V) bool {
return collectionValue < value
}
}
// ValueCastLT acts just like ValueGT, but using AnyMatcher and performing a cast on the collection value.
func ValueCastLT[T internal.Number](value T) AnyMatcher {
return func(_ any, collectionValue any) bool {
if cast, ok := collectionValue.(T); ok {
return value > cast
}
return false
}
}
// FieldEquals uses FieldMatch composed with ValueEquals as the matcher.
func FieldEquals[V any](field string, value any) AnyMatcher {
return FieldMatch[V](field, ValueDeepEquals[any, any](value))
}
// FieldMatch will attempt to retrieve the value corresponding to the given struct
// field name. V must be a struct, otherwise calls to the matcher will always return false.
// The retrieved value will be used to supply the given matcher.
func FieldMatch[V any](field string, matcher AnyMatcher) AnyMatcher {
return func(_, v any) bool {
cast, ok := v.(V)
if !ok {
return false
}
fieldVal := reflect.ValueOf(&cast).Elem()
for fieldNum := 0; fieldNum < fieldVal.NumField(); fieldNum++ {
if fieldName := fieldVal.Type().Field(fieldNum).Name; fieldName == field {
if matcher(0, fieldVal.Field(fieldNum).Interface()) {
return true
}
}
}
return false
}
}
// Not inverts the result of `matcher`
func Not[K any, V any](matcher Matcher[K, V]) Matcher[K, V] {
return func(key K, value V) bool {
return !matcher(key, value)
}
}
// And combines all the given matchers into a single matcher which returns true
// when all matchers return true.
func And[V any](matchers ...AnyMatcher) AnyMatcher {
return func(i any, collectionValue any) bool {
match := true
for _, matcher := range matchers {
match = match && matcher(i, collectionValue)
}
return match
}
}
// AndValue is similar to And, but it receives matchers wrapped by a function which
// will receive v. It is useful to compare build matchers dynamically at the execution time
// rather than at the function's call time (i.e. the composed matchers won't be called until
// the higher order matcher is called).
func AndValue[K any, V any](v V, matchers ...func(V) Matcher[K, V]) Matcher[K, V] {
return func(i K, collectionValue V) bool {
for _, matcher := range matchers {
if !matcher(v)(i, collectionValue) {
return false
}
}
return true
}
}
// Or combines all the given matchers into a single matcher which returns true
// when at least one of the given matcher returns true.
func Or[K any, V any](matchers ...Matcher[K, V]) Matcher[K, V] {
return func(i K, collectionValue V) bool {
for _, matcher := range matchers {
if matcher(i, collectionValue) {
return true
}
}
return false
}
}
// OrValue is similar to Or, but it receives matchers wrapped by a function which
// will receive v. It is useful to compare build matchers dynamically at the execution time
// rather than at the function's call time (i.e. the composed matchers won't be called until
// the higher order matcher is called).
func OrValue[K any, V any](v V, matchers ...func(V) Matcher[K, V]) Matcher[K, V] {
return func(i K, collectionValue V) bool {
for _, matcher := range matchers {
if matcher(v)(i, collectionValue) {
return true
}
}
return false
}
}
// Asc can be used as a Sort param to order collections in ascending order. It only
// works on slices holding Relational values.
func Asc[T internal.Relational]() func(T, T) bool {
return func(current, next T) bool {
return current < next
}
}
// Desc can be used as a Sort param to order collections in descending order. It only
// works on slices holding Relational values.
func Desc[T internal.Relational]() func(T, T) bool {
return func(current, next T) bool {
return current > next
}
}