9 "github.com/onsi/gomega/format"
10 errorsutil "github.com/onsi/gomega/gstruct/errors"
11 "github.com/onsi/gomega/types"
14 //MatchAllElements succeeds if every element of a slice matches the element matcher it maps to
15 //through the id function, and every element matcher is matched.
16 // Expect([]string{"a", "b"}).To(MatchAllElements(idFn, matchers.Elements{
20 func MatchAllElements(identifier Identifier, elements Elements) types.GomegaMatcher {
21 return &ElementsMatcher{
22 Identifier: identifier,
27 //MatchElements succeeds if each element of a slice matches the element matcher it maps to
28 //through the id function. It can ignore extra elements and/or missing elements.
29 // Expect([]string{"a", "c"}).To(MatchElements(idFn, IgnoreMissing|IgnoreExtra, matchers.Elements{
33 func MatchElements(identifier Identifier, options Options, elements Elements) types.GomegaMatcher {
34 return &ElementsMatcher{
35 Identifier: identifier,
37 IgnoreExtras: options&IgnoreExtras != 0,
38 IgnoreMissing: options&IgnoreMissing != 0,
39 AllowDuplicates: options&AllowDuplicates != 0,
43 // ElementsMatcher is a NestingMatcher that applies custom matchers to each element of a slice mapped
44 // by the Identifier function.
45 // TODO: Extend this to work with arrays & maps (map the key) as well.
46 type ElementsMatcher struct {
47 // Matchers for each element.
49 // Function mapping an element to the string key identifying its matcher.
52 // Whether to ignore extra elements or consider it an error.
54 // Whether to ignore missing elements or consider it an error.
56 // Whether to key duplicates when matching IDs.
63 // Element ID to matcher.
64 type Elements map[string]types.GomegaMatcher
66 // Function for identifying (mapping) elements.
67 type Identifier func(element interface{}) string
69 func (m *ElementsMatcher) Match(actual interface{}) (success bool, err error) {
70 if reflect.TypeOf(actual).Kind() != reflect.Slice {
71 return false, fmt.Errorf("%v is type %T, expected slice", actual, actual)
74 m.failures = m.matchElements(actual)
75 if len(m.failures) > 0 {
81 func (m *ElementsMatcher) matchElements(actual interface{}) (errs []error) {
82 // Provide more useful error messages in the case of a panic.
84 if err := recover(); err != nil {
85 errs = append(errs, fmt.Errorf("panic checking %+v: %v\n%s", actual, err, debug.Stack()))
89 val := reflect.ValueOf(actual)
90 elements := map[string]bool{}
91 for i := 0; i < val.Len(); i++ {
92 element := val.Index(i).Interface()
93 id := m.Identifier(element)
95 if !m.AllowDuplicates {
96 errs = append(errs, fmt.Errorf("found duplicate element ID %s", id))
102 matcher, expected := m.Elements[id]
105 errs = append(errs, fmt.Errorf("unexpected element %s", id))
110 match, err := matcher.Match(element)
116 if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
117 err = errorsutil.AggregateError(nesting.Failures())
119 err = errors.New(matcher.FailureMessage(element))
122 errs = append(errs, errorsutil.Nest(fmt.Sprintf("[%s]", id), err))
125 for id := range m.Elements {
126 if !elements[id] && !m.IgnoreMissing {
127 errs = append(errs, fmt.Errorf("missing expected element %s", id))
134 func (m *ElementsMatcher) FailureMessage(actual interface{}) (message string) {
135 failure := errorsutil.AggregateError(m.failures)
136 return format.Message(actual, fmt.Sprintf("to match elements: %v", failure))
139 func (m *ElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
140 return format.Message(actual, "not to match elements")
143 func (m *ElementsMatcher) Failures() []error {