-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmatcher.go
314 lines (240 loc) · 6.9 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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
package matcher
import (
"errors"
"fmt"
"reflect"
"regexp"
"github.com/stretchr/testify/assert"
"github.com/swaggest/assertjson"
"go.nhat.io/matcher/v3/format"
)
// Any returns a matcher that matches any value.
var Any = Func("is anything", func(actual any) (bool, error) {
return true, nil
})
// Matcher determines if the actual matches the expectation.
//
//go:generate mockery --name Matcher --output mock --outpkg mock --filename matcher.go
type Matcher interface {
Match(actual any) (bool, error)
Expected() string
}
var _ Matcher = (*equalMatcher)(nil)
// equalMatcher matches by equal string.
type equalMatcher struct {
expected any
}
// Expected returns the expectation.
func (m equalMatcher) Expected() string {
if v := strVal(m.expected); v != nil {
return *v
}
return fmt.Sprintf("%+v", m.expected)
}
// Match determines if the actual is expected.
func (m equalMatcher) Match(actual any) (bool, error) {
return assert.ObjectsAreEqual(m.expected, actual), nil
}
func (m equalMatcher) Format(s fmt.State, r rune) {
format.Format(s, r, m.expected)
}
var _ Matcher = (*jsonMatcher)(nil)
// jsonMatcher matches by json with <ignore-diff> support.
type jsonMatcher struct {
expected string
}
// Expected returns the expectation.
func (m jsonMatcher) Expected() string {
return m.expected
}
// Match determines if the actual is expected.
func (m jsonMatcher) Match(actual any) (bool, error) {
actualBytes, err := jsonVal(actual)
if err != nil {
return false, err
}
return assertjson.FailNotEqual([]byte(m.expected), actualBytes) == nil, nil
}
func (m jsonMatcher) Format(s fmt.State, r rune) {
format.Format(s, r, m.expected)
}
var _ Matcher = (*regexMatcher)(nil)
// regexMatcher matches by regex.
type regexMatcher struct {
regexp *regexp.Regexp
}
// Expected returns the expectation.
func (m regexMatcher) Expected() string {
return m.regexp.String()
}
// Match determines if the actual is expected.
func (m regexMatcher) Match(actual any) (bool, error) {
if v := strVal(actual); v != nil {
return m.regexp.MatchString(*v), nil
}
return false, nil
}
func (m regexMatcher) Format(s fmt.State, r rune) {
format.Format(s, r, m.regexp)
}
var _ Matcher = (*typeMatcher)(nil)
// typeMatcher is a .typeMatcher.
type typeMatcher struct {
typeOf reflect.Type
}
func (m typeMatcher) Match(actual any) (bool, error) {
return reflect.DeepEqual(m.typeOf, reflect.TypeOf(actual)), nil
}
func (m typeMatcher) Expected() string {
return fmt.Sprintf("type is %s", m.typeOf.String())
}
func (m typeMatcher) Format(s fmt.State, _ rune) {
_, _ = fmt.Fprintf(s, "<type is %s>", m.typeOf.String())
}
var _ Matcher = (*lenMatcher)(nil)
// lenMatcher matches by the length of the value.
type lenMatcher struct {
expected int
}
// Match determines if the actual is expected.
func (m lenMatcher) Match(actual any) (_ bool, err error) {
if actual == nil {
return false, nil
}
defer func() {
if r := recover(); r != nil {
err = errors.New(recovered(r)) // nolint: goerr113
}
}()
val := reflect.ValueOf(actual)
if val.Type().Kind() == reflect.Ptr {
val = val.Elem()
}
return val.Len() == m.expected, nil
}
// Expected returns the expectation.
func (m lenMatcher) Expected() string {
return fmt.Sprintf("len is %d", m.expected)
}
func (m lenMatcher) Format(s fmt.State, _ rune) {
_, _ = fmt.Fprintf(s, "<len is %d>", m.expected)
}
var _ Matcher = (*emptyMatcher)(nil)
// emptyMatcher checks whether the value is empty.
type emptyMatcher struct{}
// Match determines if the actual is expected.
func (emptyMatcher) Match(actual any) (bool, error) {
return isEmpty(actual), nil
}
// Expected returns the expectation.
func (emptyMatcher) Expected() string {
return "is empty"
}
func (emptyMatcher) Format(s fmt.State, _ rune) {
_, _ = s.Write([]byte("<is empty>"))
}
var _ Matcher = (*notEmptyMatcher)(nil)
// notEmptyMatcher checks whether the value is not empty.
type notEmptyMatcher struct{}
// Match determines if the actual is expected.
func (notEmptyMatcher) Match(actual any) (bool, error) {
return !isEmpty(actual), nil
}
// Expected returns the expectation.
func (notEmptyMatcher) Expected() string {
return "is not empty"
}
func (notEmptyMatcher) Format(s fmt.State, _ rune) {
_, _ = s.Write([]byte("<is not empty>"))
}
var _ Matcher = (*funcMatcher)(nil)
// funcMatcher checks by calling a function.
type funcMatcher struct {
expected string
match func(actual any) (bool, error)
}
// Match determines if the actual is expected.
func (f funcMatcher) Match(actual any) (bool, error) {
return f.match(actual)
}
// Expected returns the expectation.
func (f funcMatcher) Expected() string {
return f.expected
}
func (f funcMatcher) Format(s fmt.State, _ rune) {
_, _ = fmt.Fprintf(s, "<%s>", f.expected)
}
var _ Matcher = (*Callback)(nil)
// Callback matches by calling a function.
type Callback func() Matcher
// Expected returns the expectation.
func (m Callback) Expected() string {
return m().Expected()
}
// Match determines if the actual is expected.
func (m Callback) Match(actual any) (bool, error) {
return m().Match(actual)
}
// Matcher returns the matcher.
func (m Callback) Matcher() Matcher {
return m()
}
// Equal matches two objects.
func Equal(expected any) Matcher {
return equalMatcher{expected: expected}
}
// Equalf matches two strings by the formatted expectation.
func Equalf(expected string, args ...any) Matcher {
return equalMatcher{expected: fmt.Sprintf(expected, args...)}
}
// JSON matches two json strings with <ignore-diff> support.
func JSON(expected any) Matcher {
ex, err := jsonVal(expected)
if err != nil {
panic(err)
}
return jsonMatcher{expected: string(ex)}
}
// Regex matches two strings by using regex.
func Regex[T ~string | *regexp.Regexp | regexp.Regexp](regexp T) Matcher {
return regexMatcher{regexp: regexpVal(regexp)}
}
// IsType matches two types.
func IsType[T any]() Matcher {
var t *T
return typeMatcher{typeOf: reflect.TypeOf(t).Elem()}
}
// SameTypeAs matches two types.
func SameTypeAs(expected any) Matcher {
return typeMatcher{typeOf: reflect.TypeOf(expected)}
}
// Len matches by the length of the value.
func Len[T ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64](expected T) Matcher {
return lenMatcher{expected: int(expected)}
}
// IsEmpty checks whether the value is empty.
func IsEmpty() Matcher {
return emptyMatcher{}
}
// IsNotEmpty checks whether the value is not empty.
func IsNotEmpty() Matcher {
return notEmptyMatcher{}
}
// Func matches by calling a function.
func Func(expected string, match func(actual any) (bool, error)) Matcher {
return funcMatcher{expected: expected, match: match}
}
// Match returns a matcher according to its type.
func Match(v any) Matcher {
switch val := v.(type) {
case Matcher:
return val
case func() Matcher:
return Callback(val)
case regexp.Regexp, *regexp.Regexp:
return Regex(regexpVal(val))
case fmt.Stringer:
return Equal(val.String())
}
return Equal(v)
}