initial commit
[govpp.git] / vendor / github.com / onsi / gomega / gstruct / elements.go
1 package gstruct
2
3 import (
4         "errors"
5         "fmt"
6         "reflect"
7         "runtime/debug"
8
9         "github.com/onsi/gomega/format"
10         errorsutil "github.com/onsi/gomega/gstruct/errors"
11         "github.com/onsi/gomega/types"
12 )
13
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{
17 //      "a": BeEqual("a"),
18 //      "b": BeEqual("b"),
19 //  })
20 func MatchAllElements(identifier Identifier, elements Elements) types.GomegaMatcher {
21         return &ElementsMatcher{
22                 Identifier: identifier,
23                 Elements:   elements,
24         }
25 }
26
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{
30 //      "a": BeEqual("a")
31 //      "b": BeEqual("b"),
32 //  })
33 func MatchElements(identifier Identifier, options Options, elements Elements) types.GomegaMatcher {
34         return &ElementsMatcher{
35                 Identifier:      identifier,
36                 Elements:        elements,
37                 IgnoreExtras:    options&IgnoreExtras != 0,
38                 IgnoreMissing:   options&IgnoreMissing != 0,
39                 AllowDuplicates: options&AllowDuplicates != 0,
40         }
41 }
42
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.
48         Elements Elements
49         // Function mapping an element to the string key identifying its matcher.
50         Identifier Identifier
51
52         // Whether to ignore extra elements or consider it an error.
53         IgnoreExtras bool
54         // Whether to ignore missing elements or consider it an error.
55         IgnoreMissing bool
56         // Whether to key duplicates when matching IDs.
57         AllowDuplicates bool
58
59         // State.
60         failures []error
61 }
62
63 // Element ID to matcher.
64 type Elements map[string]types.GomegaMatcher
65
66 // Function for identifying (mapping) elements.
67 type Identifier func(element interface{}) string
68
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)
72         }
73
74         m.failures = m.matchElements(actual)
75         if len(m.failures) > 0 {
76                 return false, nil
77         }
78         return true, nil
79 }
80
81 func (m *ElementsMatcher) matchElements(actual interface{}) (errs []error) {
82         // Provide more useful error messages in the case of a panic.
83         defer func() {
84                 if err := recover(); err != nil {
85                         errs = append(errs, fmt.Errorf("panic checking %+v: %v\n%s", actual, err, debug.Stack()))
86                 }
87         }()
88
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)
94                 if elements[id] {
95                         if !m.AllowDuplicates {
96                                 errs = append(errs, fmt.Errorf("found duplicate element ID %s", id))
97                                 continue
98                         }
99                 }
100                 elements[id] = true
101
102                 matcher, expected := m.Elements[id]
103                 if !expected {
104                         if !m.IgnoreExtras {
105                                 errs = append(errs, fmt.Errorf("unexpected element %s", id))
106                         }
107                         continue
108                 }
109
110                 match, err := matcher.Match(element)
111                 if match {
112                         continue
113                 }
114
115                 if err == nil {
116                         if nesting, ok := matcher.(errorsutil.NestingMatcher); ok {
117                                 err = errorsutil.AggregateError(nesting.Failures())
118                         } else {
119                                 err = errors.New(matcher.FailureMessage(element))
120                         }
121                 }
122                 errs = append(errs, errorsutil.Nest(fmt.Sprintf("[%s]", id), err))
123         }
124
125         for id := range m.Elements {
126                 if !elements[id] && !m.IgnoreMissing {
127                         errs = append(errs, fmt.Errorf("missing expected element %s", id))
128                 }
129         }
130
131         return errs
132 }
133
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))
137 }
138
139 func (m *ElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
140         return format.Message(actual, "not to match elements")
141 }
142
143 func (m *ElementsMatcher) Failures() []error {
144         return m.failures
145 }