4 | 4 |
"encoding/json"
|
5 | 5 |
"fmt"
|
6 | 6 |
"reflect"
|
7 | |
"strings"
|
8 | 7 |
|
9 | 8 |
"github.com/onsi/gomega/format"
|
10 | 9 |
)
|
11 | 10 |
|
12 | 11 |
type MatchJSONMatcher struct {
|
13 | |
JSONToMatch interface{}
|
14 | |
firstFailurePath []interface{}
|
|
12 |
JSONToMatch interface{}
|
15 | 13 |
}
|
16 | 14 |
|
17 | 15 |
func (matcher *MatchJSONMatcher) Match(actual interface{}) (success bool, err error) {
|
|
26 | 24 |
// this is guarded by prettyPrint
|
27 | 25 |
json.Unmarshal([]byte(actualString), &aval)
|
28 | 26 |
json.Unmarshal([]byte(expectedString), &eval)
|
29 | |
var equal bool
|
30 | |
equal, matcher.firstFailurePath = deepEqual(aval, eval)
|
31 | |
return equal, nil
|
|
27 |
|
|
28 |
return reflect.DeepEqual(aval, eval), nil
|
32 | 29 |
}
|
33 | 30 |
|
34 | 31 |
func (matcher *MatchJSONMatcher) FailureMessage(actual interface{}) (message string) {
|
35 | 32 |
actualString, expectedString, _ := matcher.prettyPrint(actual)
|
36 | |
return formattedMessage(format.Message(actualString, "to match JSON of", expectedString), matcher.firstFailurePath)
|
|
33 |
return format.Message(actualString, "to match JSON of", expectedString)
|
37 | 34 |
}
|
38 | 35 |
|
39 | 36 |
func (matcher *MatchJSONMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
40 | 37 |
actualString, expectedString, _ := matcher.prettyPrint(actual)
|
41 | |
return formattedMessage(format.Message(actualString, "not to match JSON of", expectedString), matcher.firstFailurePath)
|
42 | |
}
|
43 | |
|
44 | |
func formattedMessage(comparisonMessage string, failurePath []interface{}) string {
|
45 | |
var diffMessage string
|
46 | |
if len(failurePath) == 0 {
|
47 | |
diffMessage = ""
|
48 | |
} else {
|
49 | |
diffMessage = fmt.Sprintf("\n\nfirst mismatched key: %s", formattedFailurePath(failurePath))
|
50 | |
}
|
51 | |
return fmt.Sprintf("%s%s", comparisonMessage, diffMessage)
|
52 | |
}
|
53 | |
|
54 | |
func formattedFailurePath(failurePath []interface{}) string {
|
55 | |
formattedPaths := []string{}
|
56 | |
for i := len(failurePath) - 1; i >= 0; i-- {
|
57 | |
switch p := failurePath[i].(type) {
|
58 | |
case int:
|
59 | |
formattedPaths = append(formattedPaths, fmt.Sprintf(`[%d]`, p))
|
60 | |
default:
|
61 | |
if i != len(failurePath)-1 {
|
62 | |
formattedPaths = append(formattedPaths, ".")
|
63 | |
}
|
64 | |
formattedPaths = append(formattedPaths, fmt.Sprintf(`"%s"`, p))
|
65 | |
}
|
66 | |
}
|
67 | |
return strings.Join(formattedPaths, "")
|
|
38 |
return format.Message(actualString, "not to match JSON of", expectedString)
|
68 | 39 |
}
|
69 | 40 |
|
70 | 41 |
func (matcher *MatchJSONMatcher) prettyPrint(actual interface{}) (actualFormatted, expectedFormatted string, err error) {
|
|
90 | 61 |
|
91 | 62 |
return abuf.String(), ebuf.String(), nil
|
92 | 63 |
}
|
93 | |
|
94 | |
func deepEqual(a interface{}, b interface{}) (bool, []interface{}) {
|
95 | |
var errorPath []interface{}
|
96 | |
if reflect.TypeOf(a) != reflect.TypeOf(b) {
|
97 | |
return false, errorPath
|
98 | |
}
|
99 | |
|
100 | |
switch a.(type) {
|
101 | |
case []interface{}:
|
102 | |
if len(a.([]interface{})) != len(b.([]interface{})) {
|
103 | |
return false, errorPath
|
104 | |
}
|
105 | |
|
106 | |
for i, v := range a.([]interface{}) {
|
107 | |
elementEqual, keyPath := deepEqual(v, b.([]interface{})[i])
|
108 | |
if !elementEqual {
|
109 | |
return false, append(keyPath, i)
|
110 | |
}
|
111 | |
}
|
112 | |
return true, errorPath
|
113 | |
|
114 | |
case map[string]interface{}:
|
115 | |
if len(a.(map[string]interface{})) != len(b.(map[string]interface{})) {
|
116 | |
return false, errorPath
|
117 | |
}
|
118 | |
|
119 | |
for k, v := range a.(map[string]interface{}) {
|
120 | |
elementEqual, keyPath := deepEqual(v, b.(map[string]interface{})[k])
|
121 | |
if !elementEqual {
|
122 | |
return false, append(keyPath, k)
|
123 | |
}
|
124 | |
}
|
125 | |
return true, errorPath
|
126 | |
|
127 | |
default:
|
128 | |
return a == b, errorPath
|
129 | |
}
|
130 | |
}
|