1
2
3 package gstruct
4
5 import (
6 "errors"
7 "fmt"
8 "reflect"
9 "runtime/debug"
10 "strconv"
11
12 "github.com/onsi/gomega/format"
13 errorsutil "github.com/onsi/gomega/gstruct/errors"
14 "github.com/onsi/gomega/types"
15 )
16
17
18
19
20
21
22
23
24
25
26
27 func MatchAllElements(identifier Identifier, elements Elements) types.GomegaMatcher {
28 return &ElementsMatcher{
29 Identifier: identifier,
30 Elements: elements,
31 }
32 }
33
34
35
36
37
38
39
40
41
42
43
44 func MatchAllElementsWithIndex(identifier IdentifierWithIndex, elements Elements) types.GomegaMatcher {
45 return &ElementsMatcher{
46 Identifier: identifier,
47 Elements: elements,
48 }
49 }
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 func MatchElements(identifier Identifier, options Options, elements Elements) types.GomegaMatcher {
68 return &ElementsMatcher{
69 Identifier: identifier,
70 Elements: elements,
71 IgnoreExtras: options&IgnoreExtras != 0,
72 IgnoreMissing: options&IgnoreMissing != 0,
73 AllowDuplicates: options&AllowDuplicates != 0,
74 }
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 func MatchElementsWithIndex(identifier IdentifierWithIndex, options Options, elements Elements) types.GomegaMatcher {
94 return &ElementsMatcher{
95 Identifier: identifier,
96 Elements: elements,
97 IgnoreExtras: options&IgnoreExtras != 0,
98 IgnoreMissing: options&IgnoreMissing != 0,
99 AllowDuplicates: options&AllowDuplicates != 0,
100 }
101 }
102
103
104
105
106 type ElementsMatcher struct {
107
108 Elements Elements
109
110 Identifier Identify
111
112
113 IgnoreExtras bool
114
115 IgnoreMissing bool
116
117 AllowDuplicates bool
118
119
120 failures []error
121 }
122
123
124 type Elements map[string]types.GomegaMatcher
125
126
127 type Identifier func(element interface{}) string
128
129
130
131 func (i Identifier) WithIndexAndElement(index int, element interface{}) string {
132 return i(element)
133 }
134
135
136 type IdentifierWithIndex func(index int, element interface{}) string
137
138
139
140 func (i IdentifierWithIndex) WithIndexAndElement(index int, element interface{}) string {
141 return i(index, element)
142 }
143
144
145 type Identify interface {
146 WithIndexAndElement(i int, element interface{}) string
147 }
148
149
150
151 func IndexIdentity(index int, _ interface{}) string {
152 return strconv.Itoa(index)
153 }
154
155 func (m *ElementsMatcher) Match(actual interface{}) (success bool, err error) {
156 if reflect.TypeOf(actual).Kind() != reflect.Slice {
157 return false, fmt.Errorf("%v is type %T, expected slice", actual, actual)
158 }
159
160 m.failures = m.matchElements(actual)
161 if len(m.failures) > 0 {
162 return false, nil
163 }
164 return true, nil
165 }
166
167 func (m *ElementsMatcher) matchElements(actual interface{}) (errs []error) {
168
169 defer func() {
170 if err := recover(); err != nil {
171 errs = append(errs, fmt.Errorf("panic checking %+v: %v\n%s", actual, err, debug.Stack()))
172 }
173 }()
174
175 val := reflect.ValueOf(actual)
176 elements := map[string]bool{}
177 for i := 0; i < val.Len(); i++ {
178 element := val.Index(i).Interface()
179 id := m.Identifier.WithIndexAndElement(i, element)
180 if elements[id] {
181 if !m.AllowDuplicates {
182 errs = append(errs, fmt.Errorf("found duplicate element ID %s", id))
183 continue
184 }
185 }
186 elements[id] = true
187
188 matcher, expected := m.Elements[id]
189 if !expected {
190 if !m.IgnoreExtras {
191 errs = append(errs, fmt.Errorf("unexpected element %s", id))
192 }
193 continue
194 }
195
196 match, err := matcher.Match(element)
197 if match {
198 continue
199 }
200
201 if err == nil {
202 if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
203 err = errorsutil.AggregateError(nesting.Failures())
204 } else {
205 err = errors.New(matcher.FailureMessage(element))
206 }
207 }
208 errs = append(errs, errorsutil.Nest(fmt.Sprintf("[%s]", id), err))
209 }
210
211 for id := range m.Elements {
212 if !elements[id] && !m.IgnoreMissing {
213 errs = append(errs, fmt.Errorf("missing expected element %s", id))
214 }
215 }
216
217 return errs
218 }
219
220 func (m *ElementsMatcher) FailureMessage(actual interface{}) (message string) {
221 failure := errorsutil.AggregateError(m.failures)
222 return format.Message(actual, fmt.Sprintf("to match elements: %v", failure))
223 }
224
225 func (m *ElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
226 return format.Message(actual, "not to match elements")
227 }
228
229 func (m *ElementsMatcher) Failures() []error {
230 return m.failures
231 }
232
View as plain text