Skip to content

Commit ef38a15

Browse files
authored
Use json.Number in comparisons, add support for uint64 (#25)
1 parent 196f7bd commit ef38a15

File tree

12 files changed

+1661
-342
lines changed

12 files changed

+1661
-342
lines changed

.golangci.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ run:
55
linters:
66
default: all
77
disable:
8-
- nilnil
9-
- tparallel
8+
- gocyclo
9+
- errcheck
1010
- err113
11-
- errorlint
11+
- nilnil
1212
- noinlineerr
1313
- wsl_v5
1414
- funcorder
@@ -34,23 +34,29 @@ linters:
3434
- varnamelen
3535
- wrapcheck
3636
settings:
37+
gocognit:
38+
min-complexity: 50
39+
funlen:
40+
statements: 65
41+
lines: 120
3742
dupl:
3843
threshold: 100
3944
errcheck:
4045
check-type-assertions: true
4146
check-blank: true
4247
gocyclo:
4348
min-complexity: 20
44-
cyclop:
45-
max-complexity: 15
4649
misspell:
4750
locale: US
4851
unparam:
4952
check-exported: true
53+
cyclop:
54+
max-complexity: 25
5055
exclusions:
5156
generated: lax
5257
rules:
5358
- linters:
59+
- tparallel
5460
- gosec
5561
- dupl
5662
- funlen

cmd/jsoncompact/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
)
1717

1818
// The function is a bit lengthy, but I'm not sure if it would be more approachable divided in several functions.
19-
func main() { //nolint
19+
func main() {
2020
var (
2121
input, output string
2222
length int
@@ -40,7 +40,7 @@ func main() { //nolint
4040

4141
input = flag.Arg(0)
4242
if input == "" {
43-
_, _ = fmt.Fprintln(flag.CommandLine.Output(), "Missing input path argument, use `-` for stdin.") //nolint
43+
_, _ = fmt.Fprintln(flag.CommandLine.Output(), "Missing input path argument, use `-` for stdin.")
4444
flag.Usage()
4545

4646
return

compare.go

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package assertjson
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"reflect"
9+
"strings"
10+
11+
"github.com/bool64/shared"
12+
"github.com/swaggest/assertjson/diff"
13+
)
14+
15+
func (c Comparer) varCollected(s string, v interface{}) bool {
16+
if c.Vars != nil && c.Vars.IsVar(s) {
17+
if _, found := c.Vars.Get(s); !found {
18+
if n, ok := v.(json.Number); ok {
19+
v = shared.DecodeJSONNumber(n)
20+
} else if f, ok := v.(float64); ok && f == float64(int64(f)) {
21+
v = int64(f)
22+
}
23+
24+
c.Vars.Set(s, v)
25+
26+
return true
27+
}
28+
}
29+
30+
return false
31+
}
32+
33+
func (c Comparer) filterDeltas(deltas []diff.Delta, ignoreAdded bool) []diff.Delta {
34+
result := make([]diff.Delta, 0, len(deltas))
35+
36+
for _, delta := range deltas {
37+
switch v := delta.(type) {
38+
case *diff.Modified:
39+
if c.IgnoreDiff == "" && c.Vars == nil {
40+
break
41+
}
42+
43+
if s, ok := v.OldValue.(string); ok {
44+
if s == c.IgnoreDiff { // discarding ignored diff
45+
continue
46+
}
47+
48+
if c.varCollected(s, v.NewValue) {
49+
continue
50+
}
51+
}
52+
case *diff.Object:
53+
v.Deltas = c.filterDeltas(v.Deltas, ignoreAdded)
54+
if len(v.Deltas) == 0 {
55+
continue
56+
}
57+
58+
delta = v
59+
case *diff.Array:
60+
v.Deltas = c.filterDeltas(v.Deltas, ignoreAdded)
61+
if len(v.Deltas) == 0 {
62+
continue
63+
}
64+
65+
delta = v
66+
67+
case *diff.Added:
68+
if ignoreAdded {
69+
continue
70+
}
71+
}
72+
73+
result = append(result, delta)
74+
}
75+
76+
return result
77+
}
78+
79+
type df struct {
80+
deltas []diff.Delta
81+
}
82+
83+
func (df *df) Deltas() []diff.Delta {
84+
return df.deltas
85+
}
86+
87+
func (df *df) Modified() bool {
88+
return len(df.deltas) > 0
89+
}
90+
91+
func (c Comparer) filterExpected(expected []byte) ([]byte, error) {
92+
if c.Vars != nil {
93+
for k, v := range c.Vars.GetAll() {
94+
j, err := json.Marshal(v)
95+
if err != nil {
96+
return nil, fmt.Errorf("failed to marshal var %s: %w", k, err) // Not wrapping to support go1.12.
97+
}
98+
99+
expected = bytes.ReplaceAll(expected, []byte(`"`+k+`"`), j)
100+
}
101+
}
102+
103+
return expected, nil
104+
}
105+
106+
func (c Comparer) compare(expDecoded, actDecoded interface{}) (diff.Diff, error) {
107+
switch v := expDecoded.(type) {
108+
case []interface{}:
109+
if actArray, ok := actDecoded.([]interface{}); ok {
110+
return diff.New().CompareArrays(v, actArray), nil
111+
}
112+
113+
return nil, errors.New("types mismatch, array expected")
114+
115+
case map[string]interface{}:
116+
if actObject, ok := actDecoded.(map[string]interface{}); ok {
117+
return diff.New().CompareObjects(v, actObject), nil
118+
}
119+
120+
return nil, errors.New("types mismatch, object expected")
121+
122+
default:
123+
if !reflect.DeepEqual(expDecoded, actDecoded) { // scalar value comparison
124+
return nil, fmt.Errorf("values %v and %v are not equal", expDecoded, actDecoded)
125+
}
126+
}
127+
128+
return nil, nil
129+
}
130+
131+
func unmarshal(data []byte, decoded interface{}) error {
132+
dec := json.NewDecoder(bytes.NewReader(data))
133+
dec.UseNumber()
134+
135+
return dec.Decode(decoded)
136+
}
137+
138+
func (c Comparer) fail(expected, actual []byte, ignoreAdded bool) error {
139+
var expDecoded, actDecoded interface{}
140+
141+
expected, err := c.filterExpected(expected)
142+
if err != nil {
143+
return err
144+
}
145+
146+
err = unmarshal(expected, &expDecoded)
147+
if err != nil {
148+
return fmt.Errorf("failed to unmarshal expected:\n%wv", err)
149+
}
150+
151+
err = unmarshal(actual, &actDecoded)
152+
if err != nil {
153+
return fmt.Errorf("failed to unmarshal actual:\n%wv", err)
154+
}
155+
156+
if s, ok := expDecoded.(string); ok && c.Vars != nil && c.Vars.IsVar(s) {
157+
if c.varCollected(s, actDecoded) {
158+
return nil
159+
}
160+
161+
if v, found := c.Vars.Get(s); found {
162+
expDecoded = v
163+
}
164+
}
165+
166+
diffValue, err := c.compare(expDecoded, actDecoded)
167+
if err != nil {
168+
return err
169+
}
170+
171+
if diffValue == nil {
172+
return nil
173+
}
174+
175+
if !diffValue.Modified() {
176+
return nil
177+
}
178+
179+
diffValue = &df{deltas: c.filterDeltas(diffValue.Deltas(), ignoreAdded)}
180+
if !diffValue.Modified() {
181+
return nil
182+
}
183+
184+
diffText, err := diff.NewASCIIFormatter(expDecoded, c.FormatterConfig).Format(diffValue)
185+
if err != nil {
186+
return fmt.Errorf("failed to format diff:\n%wv", err)
187+
}
188+
189+
diffText = c.reduceDiff(diffText)
190+
191+
return errors.New("not equal:\n" + diffText)
192+
}
193+
194+
func (c Comparer) reduceDiff(diffText string) string {
195+
if c.KeepFullDiff {
196+
return diffText
197+
}
198+
199+
if c.FullDiffMaxLines == 0 {
200+
c.FullDiffMaxLines = 50
201+
}
202+
203+
if c.DiffSurroundingLines == 0 {
204+
c.DiffSurroundingLines = 5
205+
}
206+
207+
diffRows := strings.Split(diffText, "\n")
208+
if len(diffRows) <= c.FullDiffMaxLines {
209+
return diffText
210+
}
211+
212+
var result []string
213+
214+
prev := 0
215+
216+
for i, r := range diffRows {
217+
if len(r) == 0 {
218+
continue
219+
}
220+
221+
if r[0] == '-' || r[0] == '+' {
222+
start := i - c.DiffSurroundingLines
223+
if start < prev {
224+
start = prev
225+
} else if start > prev {
226+
result = append(result, "...")
227+
}
228+
229+
end := i + c.DiffSurroundingLines
230+
if end >= len(diffRows) {
231+
end = len(diffRows) - 1
232+
}
233+
234+
prev = end
235+
236+
for k := start; k < end; k++ {
237+
result = append(result, diffRows[k])
238+
}
239+
}
240+
}
241+
242+
if prev < len(diffRows)-1 {
243+
result = append(result, "...")
244+
}
245+
246+
return strings.Join(result, "\n")
247+
}

0 commit comments

Comments
 (0)