1package internal
2
3import (
4	"fmt"
5	"reflect"
6
7	"github.com/onsi/gomega/types"
8)
9
10type Assertion struct {
11	actuals     []interface{} // actual value plus all extra values
12	actualIndex int           // value to pass to the matcher
13	vet         vetinari      // the vet to call before calling Gomega matcher
14	offset      int
15	g           *Gomega
16}
17
18// ...obligatory discworld reference, as "vetineer" doesn't sound ... quite right.
19type vetinari func(assertion *Assertion, optionalDescription ...interface{}) bool
20
21func NewAssertion(actualInput interface{}, g *Gomega, offset int, extra ...interface{}) *Assertion {
22	return &Assertion{
23		actuals:     append([]interface{}{actualInput}, extra...),
24		actualIndex: 0,
25		vet:         (*Assertion).vetActuals,
26		offset:      offset,
27		g:           g,
28	}
29}
30
31func (assertion *Assertion) WithOffset(offset int) types.Assertion {
32	assertion.offset = offset
33	return assertion
34}
35
36func (assertion *Assertion) Error() types.Assertion {
37	return &Assertion{
38		actuals:     assertion.actuals,
39		actualIndex: len(assertion.actuals) - 1,
40		vet:         (*Assertion).vetError,
41		offset:      assertion.offset,
42		g:           assertion.g,
43	}
44}
45
46func (assertion *Assertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
47	assertion.g.THelper()
48	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
49}
50
51func (assertion *Assertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
52	assertion.g.THelper()
53	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
54}
55
56func (assertion *Assertion) To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
57	assertion.g.THelper()
58	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
59}
60
61func (assertion *Assertion) ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
62	assertion.g.THelper()
63	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
64}
65
66func (assertion *Assertion) NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
67	assertion.g.THelper()
68	return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
69}
70
71func (assertion *Assertion) buildDescription(optionalDescription ...interface{}) string {
72	switch len(optionalDescription) {
73	case 0:
74		return ""
75	case 1:
76		if describe, ok := optionalDescription[0].(func() string); ok {
77			return describe() + "\n"
78		}
79	}
80	return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
81}
82
83func (assertion *Assertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
84	actualInput := assertion.actuals[assertion.actualIndex]
85	matches, err := matcher.Match(actualInput)
86	assertion.g.THelper()
87	if err != nil {
88		description := assertion.buildDescription(optionalDescription...)
89		assertion.g.Fail(description+err.Error(), 2+assertion.offset)
90		return false
91	}
92	if matches != desiredMatch {
93		var message string
94		if desiredMatch {
95			message = matcher.FailureMessage(actualInput)
96		} else {
97			message = matcher.NegatedFailureMessage(actualInput)
98		}
99		description := assertion.buildDescription(optionalDescription...)
100		assertion.g.Fail(description+message, 2+assertion.offset)
101		return false
102	}
103
104	return true
105}
106
107// vetActuals vets the actual values, with the (optional) exception of a
108// specific value, such as the first value in case non-error assertions, or the
109// last value in case of Error()-based assertions.
110func (assertion *Assertion) vetActuals(optionalDescription ...interface{}) bool {
111	success, message := vetActuals(assertion.actuals, assertion.actualIndex)
112	if success {
113		return true
114	}
115
116	description := assertion.buildDescription(optionalDescription...)
117	assertion.g.THelper()
118	assertion.g.Fail(description+message, 2+assertion.offset)
119	return false
120}
121
122// vetError vets the actual values, except for the final error value, in case
123// the final error value is non-zero. Otherwise, it doesn't vet the actual
124// values, as these are allowed to take on any values unless there is a non-zero
125// error value.
126func (assertion *Assertion) vetError(optionalDescription ...interface{}) bool {
127	if err := assertion.actuals[assertion.actualIndex]; err != nil {
128		// Go error result idiom: all other actual values must be zero values.
129		return assertion.vetActuals(optionalDescription...)
130	}
131	return true
132}
133
134// vetActuals vets a slice of actual values, optionally skipping a particular
135// value slice element, such as the first or last value slice element.
136func vetActuals(actuals []interface{}, skipIndex int) (bool, string) {
137	for i, actual := range actuals {
138		if i == skipIndex {
139			continue
140		}
141		if actual != nil {
142			zeroValue := reflect.Zero(reflect.TypeOf(actual)).Interface()
143			if !reflect.DeepEqual(zeroValue, actual) {
144				message := fmt.Sprintf("Unexpected non-nil/non-zero argument at index %d:\n\t<%T>: %#v", i, actual, actual)
145				return false, message
146			}
147		}
148	}
149	return true, ""
150}
151