1package assertions
2
3import (
4	"fmt"
5	"reflect"
6
7	"github.com/smartystreets/assertions/internal/oglematchers"
8)
9
10// ShouldContain receives exactly two parameters. The first is a slice and the
11// second is a proposed member. Membership is determined using ShouldEqual.
12func ShouldContain(actual interface{}, expected ...interface{}) string {
13	if fail := need(1, expected); fail != success {
14		return fail
15	}
16
17	if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil {
18		typeName := reflect.TypeOf(actual)
19
20		if fmt.Sprintf("%v", matchError) == "which is not a slice or array" {
21			return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName)
22		}
23		return fmt.Sprintf(shouldHaveContained, typeName, expected[0])
24	}
25	return success
26}
27
28// ShouldNotContain receives exactly two parameters. The first is a slice and the
29// second is a proposed member. Membership is determinied using ShouldEqual.
30func ShouldNotContain(actual interface{}, expected ...interface{}) string {
31	if fail := need(1, expected); fail != success {
32		return fail
33	}
34	typeName := reflect.TypeOf(actual)
35
36	if matchError := oglematchers.Contains(expected[0]).Matches(actual); matchError != nil {
37		if fmt.Sprintf("%v", matchError) == "which is not a slice or array" {
38			return fmt.Sprintf(shouldHaveBeenAValidCollection, typeName)
39		}
40		return success
41	}
42	return fmt.Sprintf(shouldNotHaveContained, typeName, expected[0])
43}
44
45// ShouldContainKey receives exactly two parameters. The first is a map and the
46// second is a proposed key. Keys are compared with a simple '=='.
47func ShouldContainKey(actual interface{}, expected ...interface{}) string {
48	if fail := need(1, expected); fail != success {
49		return fail
50	}
51
52	keys, isMap := mapKeys(actual)
53	if !isMap {
54		return fmt.Sprintf(shouldHaveBeenAValidMap, reflect.TypeOf(actual))
55	}
56
57	if !keyFound(keys, expected[0]) {
58		return fmt.Sprintf(shouldHaveContainedKey, reflect.TypeOf(actual), expected)
59	}
60
61	return ""
62}
63
64// ShouldNotContainKey receives exactly two parameters. The first is a map and the
65// second is a proposed absent key. Keys are compared with a simple '=='.
66func ShouldNotContainKey(actual interface{}, expected ...interface{}) string {
67	if fail := need(1, expected); fail != success {
68		return fail
69	}
70
71	keys, isMap := mapKeys(actual)
72	if !isMap {
73		return fmt.Sprintf(shouldHaveBeenAValidMap, reflect.TypeOf(actual))
74	}
75
76	if keyFound(keys, expected[0]) {
77		return fmt.Sprintf(shouldNotHaveContainedKey, reflect.TypeOf(actual), expected)
78	}
79
80	return ""
81}
82
83func mapKeys(m interface{}) ([]reflect.Value, bool) {
84	value := reflect.ValueOf(m)
85	if value.Kind() != reflect.Map {
86		return nil, false
87	}
88	return value.MapKeys(), true
89}
90func keyFound(keys []reflect.Value, expectedKey interface{}) bool {
91	found := false
92	for _, key := range keys {
93		if key.Interface() == expectedKey {
94			found = true
95		}
96	}
97	return found
98}
99
100// ShouldBeIn receives at least 2 parameters. The first is a proposed member of the collection
101// that is passed in either as the second parameter, or of the collection that is comprised
102// of all the remaining parameters. This assertion ensures that the proposed member is in
103// the collection (using ShouldEqual).
104func ShouldBeIn(actual interface{}, expected ...interface{}) string {
105	if fail := atLeast(1, expected); fail != success {
106		return fail
107	}
108
109	if len(expected) == 1 {
110		return shouldBeIn(actual, expected[0])
111	}
112	return shouldBeIn(actual, expected)
113}
114func shouldBeIn(actual interface{}, expected interface{}) string {
115	if matchError := oglematchers.Contains(actual).Matches(expected); matchError != nil {
116		return fmt.Sprintf(shouldHaveBeenIn, actual, reflect.TypeOf(expected))
117	}
118	return success
119}
120
121// ShouldNotBeIn receives at least 2 parameters. The first is a proposed member of the collection
122// that is passed in either as the second parameter, or of the collection that is comprised
123// of all the remaining parameters. This assertion ensures that the proposed member is NOT in
124// the collection (using ShouldEqual).
125func ShouldNotBeIn(actual interface{}, expected ...interface{}) string {
126	if fail := atLeast(1, expected); fail != success {
127		return fail
128	}
129
130	if len(expected) == 1 {
131		return shouldNotBeIn(actual, expected[0])
132	}
133	return shouldNotBeIn(actual, expected)
134}
135func shouldNotBeIn(actual interface{}, expected interface{}) string {
136	if matchError := oglematchers.Contains(actual).Matches(expected); matchError == nil {
137		return fmt.Sprintf(shouldNotHaveBeenIn, actual, reflect.TypeOf(expected))
138	}
139	return success
140}
141
142// ShouldBeEmpty receives a single parameter (actual) and determines whether or not
143// calling len(actual) would return `0`. It obeys the rules specified by the len
144// function for determining length: http://golang.org/pkg/builtin/#len
145func ShouldBeEmpty(actual interface{}, expected ...interface{}) string {
146	if fail := need(0, expected); fail != success {
147		return fail
148	}
149
150	if actual == nil {
151		return success
152	}
153
154	value := reflect.ValueOf(actual)
155	switch value.Kind() {
156	case reflect.Slice:
157		if value.Len() == 0 {
158			return success
159		}
160	case reflect.Chan:
161		if value.Len() == 0 {
162			return success
163		}
164	case reflect.Map:
165		if value.Len() == 0 {
166			return success
167		}
168	case reflect.String:
169		if value.Len() == 0 {
170			return success
171		}
172	case reflect.Ptr:
173		elem := value.Elem()
174		kind := elem.Kind()
175		if (kind == reflect.Slice || kind == reflect.Array) && elem.Len() == 0 {
176			return success
177		}
178	}
179
180	return fmt.Sprintf(shouldHaveBeenEmpty, actual)
181}
182
183// ShouldNotBeEmpty receives a single parameter (actual) and determines whether or not
184// calling len(actual) would return a value greater than zero. It obeys the rules
185// specified by the `len` function for determining length: http://golang.org/pkg/builtin/#len
186func ShouldNotBeEmpty(actual interface{}, expected ...interface{}) string {
187	if fail := need(0, expected); fail != success {
188		return fail
189	}
190
191	if empty := ShouldBeEmpty(actual, expected...); empty != success {
192		return success
193	}
194	return fmt.Sprintf(shouldNotHaveBeenEmpty, actual)
195}
196
197// ShouldHaveLength receives 2 parameters. The first is a collection to check
198// the length of, the second being the expected length. It obeys the rules
199// specified by the len function for determining length:
200// http://golang.org/pkg/builtin/#len
201func ShouldHaveLength(actual interface{}, expected ...interface{}) string {
202	if fail := need(1, expected); fail != success {
203		return fail
204	}
205
206	var expectedLen int64
207	lenValue := reflect.ValueOf(expected[0])
208	switch lenValue.Kind() {
209	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
210		expectedLen = lenValue.Int()
211	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
212		expectedLen = int64(lenValue.Uint())
213	default:
214		return fmt.Sprintf(shouldHaveBeenAValidInteger, reflect.TypeOf(expected[0]))
215	}
216
217	if expectedLen < 0 {
218		return fmt.Sprintf(shouldHaveBeenAValidLength, expected[0])
219	}
220
221	value := reflect.ValueOf(actual)
222	switch value.Kind() {
223	case reflect.Slice:
224		if int64(value.Len()) == expectedLen {
225			return success
226		}
227	case reflect.Chan:
228		if int64(value.Len()) == expectedLen {
229			return success
230		}
231	case reflect.Map:
232		if int64(value.Len()) == expectedLen {
233			return success
234		}
235	case reflect.String:
236		if int64(value.Len()) == expectedLen {
237			return success
238		}
239	case reflect.Ptr:
240		elem := value.Elem()
241		kind := elem.Kind()
242		if (kind == reflect.Slice || kind == reflect.Array) && int64(elem.Len()) == expectedLen {
243			return success
244		}
245	default:
246		return fmt.Sprintf(shouldHaveBeenAValidCollection, reflect.TypeOf(actual))
247	}
248
249	return fmt.Sprintf(shouldHaveHadLength, actual, expectedLen)
250}
251