Skip to content

Commit 989cd42

Browse files
author
Reuven
committed
work-in-progress
1 parent 9cab8bb commit 989cd42

54 files changed

Lines changed: 869 additions & 152 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

diff/callbacks_diff.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,30 @@ func (callbacksDiff *CallbacksDiff) Empty() bool {
2525
len(callbacksDiff.Modified) == 0
2626
}
2727

28+
// Breaking indicates whether this element includes a breaking change
29+
func (diff *CallbacksDiff) Breaking() bool {
30+
if diff.Empty() {
31+
return false
32+
}
33+
34+
return len(diff.Deleted) > 0 ||
35+
diff.Modified.Breaking()
36+
}
37+
2838
// ModifiedCallbacks is map of callback names to their respective diffs
2939
type ModifiedCallbacks map[string]*PathsDiff
3040

41+
// Breaking indicates whether this element includes a breaking change
42+
func (diff ModifiedCallbacks) Breaking() bool {
43+
for _, pathsDiff := range diff {
44+
if pathsDiff.Breaking() {
45+
return true
46+
}
47+
}
48+
49+
return false
50+
}
51+
3152
func newCallbacksDiff() *CallbacksDiff {
3253
return &CallbacksDiff{
3354
Added: StringList{},
@@ -47,6 +68,10 @@ func getCallbacksDiff(config *Config, callbacks1, callbacks2 openapi3.Callbacks)
4768
return nil, nil
4869
}
4970

71+
if config.BreakingOnly && !diff.Breaking() {
72+
return nil, nil
73+
}
74+
5075
return diff, nil
5176
}
5277

diff/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ type Config struct {
77
IncludeExtensions StringSet // which extensions to include in the diff (default is none) - see https://swagger.io/specification/#specification-extensions
88
PathFilter string // diff will only include paths that match this regex (optional)
99
PathPrefix string // a prefix that exists in first spec paths but not in second one (optional)
10+
BreakingOnly bool // whether to calc breaking changes only
11+
1012
}
1113

1214
// NewConfig returns a default configuration
@@ -15,5 +17,6 @@ func NewConfig() *Config {
1517
ExcludeExamples: false,
1618
ExcludeDescription: false,
1719
IncludeExtensions: StringSet{},
20+
BreakingOnly: false,
1821
}
1922
}

diff/contact.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@ func (diff *ContactDiff) Empty() bool {
1717
return diff == nil || *diff == ContactDiff{}
1818
}
1919

20+
// Breaking indicates whether this element includes a breaking change
21+
func (diff *ContactDiff) Breaking() bool {
22+
return false
23+
}
24+
2025
func getContactDiff(config *Config, contact1, contact2 *openapi3.Contact) *ContactDiff {
2126
diff := getContactDiffInternal(config, contact1, contact2)
2227

2328
if diff.Empty() {
2429
return nil
2530
}
31+
32+
if config.BreakingOnly && !diff.Breaking() {
33+
return nil
34+
}
35+
2636
return diff
2737
}
2838

@@ -45,9 +55,9 @@ func getContactDiffInternal(config *Config, contact1, contact2 *openapi3.Contact
4555
}
4656

4757
result.ExtensionsDiff = getExtensionsDiff(config, contact1.ExtensionProps, contact2.ExtensionProps)
48-
result.NameDiff = getValueDiff(contact1.Name, contact2.Name)
49-
result.URLDiff = getValueDiff(contact1.URL, contact2.URL)
50-
result.EmailDiff = getValueDiff(contact1.Email, contact2.Email)
58+
result.NameDiff = getValueDiff(config, false, contact1.Name, contact2.Name)
59+
result.URLDiff = getValueDiff(config, false, contact1.URL, contact2.URL)
60+
result.EmailDiff = getValueDiff(config, false, contact1.Email, contact2.Email)
5161

5262
return &result
5363
}

diff/content_diff.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ type ContentDiff struct {
1414
// ModifiedMediaTypes is map of media type names to their respective diffs
1515
type ModifiedMediaTypes map[string]*MediaTypeDiff
1616

17+
// Breaking indicates whether this element includes a breaking change
18+
func (diff ModifiedMediaTypes) Breaking() bool {
19+
for _, mediaTypeDiff := range diff {
20+
if mediaTypeDiff.Breaking() {
21+
return true
22+
}
23+
}
24+
return false
25+
}
26+
1727
func newContentDiff() *ContentDiff {
1828
return &ContentDiff{
1929
MediaTypeAdded: StringList{},
@@ -33,6 +43,16 @@ func (diff *ContentDiff) Empty() bool {
3343
len(diff.MediaTypeModified) == 0
3444
}
3545

46+
// Breaking indicates whether this element includes a breaking change
47+
func (diff *ContentDiff) Breaking() bool {
48+
if diff.Empty() {
49+
return false
50+
}
51+
52+
return len(diff.MediaTypeDeleted) > 0 ||
53+
diff.MediaTypeModified.Breaking()
54+
}
55+
3656
func getContentDiff(config *Config, content1, content2 openapi3.Content) (*ContentDiff, error) {
3757
diff, err := getContentDiffInternal(config, content1, content2)
3858
if err != nil {
@@ -42,6 +62,11 @@ func getContentDiff(config *Config, content1, content2 openapi3.Content) (*Conte
4262
if diff.Empty() {
4363
return nil, nil
4464
}
65+
66+
if config.BreakingOnly && !diff.Breaking() {
67+
return nil, nil
68+
}
69+
4570
return diff, nil
4671
}
4772

diff/diff.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ func getDiff(config *Config, s1, s2 *openapi3.T) (*Diff, error) {
7070
return nil, nil
7171
}
7272

73+
if config.BreakingOnly && !diff.Breaking() {
74+
return nil, nil
75+
}
76+
7377
return diff, nil
7478
}
7579

@@ -79,7 +83,7 @@ func getDiffInternal(config *Config, s1, s2 *openapi3.T) (*Diff, error) {
7983
var err error
8084

8185
result.ExtensionsDiff = getExtensionsDiff(config, s1.ExtensionProps, s2.ExtensionProps)
82-
result.OpenAPIDiff = getValueDiff(s1.OpenAPI, s2.OpenAPI)
86+
result.OpenAPIDiff = getValueDiff(config, false, s1.OpenAPI, s2.OpenAPI)
8387

8488
result.InfoDiff, err = getInfoDiff(config, s1.Info, s2.Info)
8589
if err != nil {

diff/diff_breaking_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package diff_test
33
import (
44
"testing"
55

6+
"github.com/getkin/kin-openapi/openapi3"
67
"github.com/stretchr/testify/require"
78
"github.com/tufin/oasdiff/diff"
89
)
@@ -14,3 +15,37 @@ func TestBreaking_Same(t *testing.T) {
1415
func TestBreaking_DeletedPaths(t *testing.T) {
1516
require.True(t, d(t, diff.NewConfig(), 1, 2).Breaking())
1617
}
18+
19+
func TestBreaking_DeletedTagAllChanges(t *testing.T) {
20+
require.False(t, d(t, &diff.Config{
21+
BreakingOnly: false,
22+
}, 1, 5).PathsDiff.Modified[securityScorePath].OperationsDiff.Modified["GET"].TagsDiff.Empty())
23+
}
24+
25+
func TestBreaking_DeletedTag(t *testing.T) {
26+
require.True(t, d(t, &diff.Config{
27+
BreakingOnly: true,
28+
}, 1, 5).PathsDiff.Modified[securityScorePath].OperationsDiff.Modified["GET"].TagsDiff.Empty())
29+
}
30+
31+
func TestBreaking_DeletedEnum(t *testing.T) {
32+
require.False(t,
33+
d(t, &diff.Config{
34+
BreakingOnly: true,
35+
}, 3, 1).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ParametersDiff.Modified[openapi3.ParameterInPath]["project"].SchemaDiff.EnumDiff.Empty())
36+
}
37+
38+
func TestBreaking_AddedEnum(t *testing.T) {
39+
require.False(t,
40+
d(t, &diff.Config{
41+
BreakingOnly: true,
42+
}, 1, 3).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ParametersDiff.Modified[openapi3.ParameterInPath].Breaking())
43+
}
44+
45+
func TestBreaking_ModifiedExtension(t *testing.T) {
46+
config := diff.Config{
47+
IncludeExtensions: diff.StringSet{"x-extension-test2": struct{}{}},
48+
}
49+
50+
require.False(t, d(t, &config, 1, 3).ExtensionsDiff.Breaking())
51+
}

diff/diff_test.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import (
99
"github.com/tufin/oasdiff/diff"
1010
)
1111

12-
const securityScorePath = "/api/{domain}/{project}/badges/security-score"
12+
const (
13+
securityScorePath = "/api/{domain}/{project}/badges/security-score"
14+
securityScorePathSlash = securityScorePath + "/"
15+
installCommandPath = "/api/{domain}/{project}/install-command"
16+
)
1317

1418
func l(t *testing.T, v int) *openapi3.T {
1519
loader := openapi3.NewLoader()
@@ -49,7 +53,7 @@ func TestDiff_Empty(t *testing.T) {
4953

5054
func TestDiff_DeletedPaths(t *testing.T) {
5155
require.ElementsMatch(t,
52-
[]string{"/api/{domain}/{project}/install-command", "/register", "/subscribe"},
56+
[]string{installCommandPath, "/register", "/subscribe"},
5357
d(t, diff.NewConfig(), 1, 2).PathsDiff.Deleted)
5458
}
5559

@@ -61,7 +65,7 @@ func TestDiff_AddedOperation(t *testing.T) {
6165

6266
func TestDiff_DeletedOperation(t *testing.T) {
6367
require.Contains(t,
64-
d(t, diff.NewConfig(), 2, 1).PathsDiff.Modified["/api/{domain}/{project}/badges/security-score/"].OperationsDiff.Deleted,
68+
d(t, diff.NewConfig(), 2, 1).PathsDiff.Modified[securityScorePathSlash].OperationsDiff.Deleted,
6569
"POST")
6670
}
6771

@@ -134,7 +138,7 @@ func TestDiff_ModifiedEncodingHeaders(t *testing.T) {
134138

135139
func TestDiff_AddedParam(t *testing.T) {
136140
require.Contains(t,
137-
d(t, diff.NewConfig(), 2, 1).PathsDiff.Modified["/api/{domain}/{project}/badges/security-score/"].OperationsDiff.Modified["GET"].ParametersDiff.Added[openapi3.ParameterInHeader],
141+
d(t, diff.NewConfig(), 2, 1).PathsDiff.Modified[securityScorePathSlash].OperationsDiff.Modified["GET"].ParametersDiff.Added[openapi3.ParameterInHeader],
138142
"X-Auth-Name")
139143
}
140144

@@ -150,7 +154,7 @@ func TestDiff_ModifiedParam(t *testing.T) {
150154
From: true,
151155
To: (interface{})(nil),
152156
},
153-
d(t, diff.NewConfig(), 2, 1).PathsDiff.Modified["/api/{domain}/{project}/badges/security-score/"].OperationsDiff.Modified["GET"].ParametersDiff.Modified[openapi3.ParameterInQuery]["image"].ExplodeDiff)
157+
d(t, diff.NewConfig(), 2, 1).PathsDiff.Modified[securityScorePathSlash].OperationsDiff.Modified["GET"].ParametersDiff.Modified[openapi3.ParameterInQuery]["image"].ExplodeDiff)
154158
}
155159

156160
func TestSchemaDiff_TypeDiff(t *testing.T) {
@@ -168,7 +172,7 @@ func TestSchemaDiff_EnumDiff(t *testing.T) {
168172
Added: diff.EnumValues{"test1"},
169173
Deleted: diff.EnumValues{},
170174
},
171-
d(t, diff.NewConfig(), 1, 3).PathsDiff.Modified["/api/{domain}/{project}/install-command"].OperationsDiff.Modified["GET"].ParametersDiff.Modified[openapi3.ParameterInPath]["project"].SchemaDiff.EnumDiff)
175+
d(t, diff.NewConfig(), 1, 3).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ParametersDiff.Modified[openapi3.ParameterInPath]["project"].SchemaDiff.EnumDiff)
172176
}
173177

174178
func TestSchemaDiff_RequiredAdded(t *testing.T) {
@@ -195,7 +199,7 @@ func TestSchemaDiff_ContentDiff(t *testing.T) {
195199
From: "number",
196200
To: "string",
197201
},
198-
d(t, diff.NewConfig(), 2, 1).PathsDiff.Modified["/api/{domain}/{project}/badges/security-score/"].OperationsDiff.Modified["GET"].ParametersDiff.Modified[openapi3.ParameterInQuery]["filter"].ContentDiff.MediaTypeModified["application/json"].SchemaDiff.PropertiesDiff.Modified["color"].TypeDiff)
202+
d(t, diff.NewConfig(), 2, 1).PathsDiff.Modified[securityScorePathSlash].OperationsDiff.Modified["GET"].ParametersDiff.Modified[openapi3.ParameterInQuery]["filter"].ContentDiff.MediaTypeModified["application/json"].SchemaDiff.PropertiesDiff.Modified["color"].TypeDiff)
199203
}
200204

201205
func TestSchemaDiff_MediaTypeAdded(t *testing.T) {
@@ -284,7 +288,7 @@ func TestResponseDescriptionModified(t *testing.T) {
284288
From: "Tufin",
285289
To: "Tufin1",
286290
},
287-
d(t, &config, 3, 1).PathsDiff.Modified["/api/{domain}/{project}/install-command"].OperationsDiff.Modified["GET"].ResponsesDiff.Modified["default"].DescriptionDiff)
291+
d(t, &config, 3, 1).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ResponsesDiff.Modified["default"].DescriptionDiff)
288292
}
289293

290294
func TestResponseHeadersModified(t *testing.T) {
@@ -293,18 +297,18 @@ func TestResponseHeadersModified(t *testing.T) {
293297
From: "Request limit per min.",
294298
To: "Request limit per hour.",
295299
},
296-
d(t, diff.NewConfig(), 3, 1).PathsDiff.Modified["/api/{domain}/{project}/install-command"].OperationsDiff.Modified["GET"].ResponsesDiff.Modified["default"].HeadersDiff.Modified["X-RateLimit-Limit"].DescriptionDiff)
300+
d(t, diff.NewConfig(), 3, 1).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ResponsesDiff.Modified["default"].HeadersDiff.Modified["X-RateLimit-Limit"].DescriptionDiff)
297301
}
298302

299303
func TestServerAdded(t *testing.T) {
300304
require.Contains(t,
301-
d(t, diff.NewConfig(), 5, 3).PathsDiff.Modified["/api/{domain}/{project}/install-command"].OperationsDiff.Modified["GET"].ServersDiff.Added,
305+
d(t, diff.NewConfig(), 5, 3).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ServersDiff.Added,
302306
"https://tufin.io/securecloud")
303307
}
304308

305309
func TestServerDeleted(t *testing.T) {
306310
require.Contains(t,
307-
d(t, diff.NewConfig(), 3, 5).PathsDiff.Modified["/api/{domain}/{project}/install-command"].OperationsDiff.Modified["GET"].ServersDiff.Deleted,
311+
d(t, diff.NewConfig(), 3, 5).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ServersDiff.Deleted,
308312
"https://tufin.io/securecloud")
309313
}
310314

@@ -314,13 +318,13 @@ func TestServerModified(t *testing.T) {
314318
}
315319

316320
require.Contains(t,
317-
d(t, &config, 5, 3).PathsDiff.Modified["/api/{domain}/{project}/install-command"].OperationsDiff.Modified["GET"].ServersDiff.Modified,
321+
d(t, &config, 5, 3).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ServersDiff.Modified,
318322
"https://www.tufin.io/securecloud")
319323
}
320324

321325
func TestServerVariableAdded(t *testing.T) {
322326
require.Contains(t,
323-
d(t, diff.NewConfig(), 3, 5).PathsDiff.Modified["/api/{domain}/{project}/install-command"].OperationsDiff.Modified["GET"].ServersDiff.Modified["https://www.tufin.io/securecloud"].VariablesDiff.Added,
327+
d(t, diff.NewConfig(), 3, 5).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ServersDiff.Modified["https://www.tufin.io/securecloud"].VariablesDiff.Added,
324328
"name")
325329
}
326330

@@ -330,18 +334,18 @@ func TestServerVariableModified(t *testing.T) {
330334
From: "CEO",
331335
To: "developer",
332336
},
333-
d(t, diff.NewConfig(), 3, 5).PathsDiff.Modified["/api/{domain}/{project}/install-command"].OperationsDiff.Modified["GET"].ServersDiff.Modified["https://www.tufin.io/securecloud"].VariablesDiff.Modified["title"].DefaultDiff)
337+
d(t, diff.NewConfig(), 3, 5).PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ServersDiff.Modified["https://www.tufin.io/securecloud"].VariablesDiff.Modified["title"].DefaultDiff)
334338
}
335339

336340
func TestServerAddedToPathItem(t *testing.T) {
337341
require.Contains(t,
338-
d(t, diff.NewConfig(), 5, 3).PathsDiff.Modified["/api/{domain}/{project}/install-command"].ServersDiff.Added,
342+
d(t, diff.NewConfig(), 5, 3).PathsDiff.Modified[installCommandPath].ServersDiff.Added,
339343
"https://tufin.io/securecloud")
340344
}
341345

342346
func TestParamAddedToPathItem(t *testing.T) {
343347
require.Contains(t,
344-
d(t, diff.NewConfig(), 5, 3).PathsDiff.Modified["/api/{domain}/{project}/install-command"].ParametersDiff.Added[openapi3.ParameterInHeader],
348+
d(t, diff.NewConfig(), 5, 3).PathsDiff.Modified[installCommandPath].ParametersDiff.Added[openapi3.ParameterInHeader],
345349
"name")
346350
}
347351

@@ -402,7 +406,7 @@ func TestResponseContentModified(t *testing.T) {
402406

403407
func TestResponseDespcriptionNil(t *testing.T) {
404408
s3 := l(t, 3)
405-
s3.Paths["/api/{domain}/{project}/install-command"].Get.Responses["default"].Value.Description = nil
409+
s3.Paths[installCommandPath].Get.Responses["default"].Value.Description = nil
406410

407411
d, err := diff.Get(diff.NewConfig(), s3, l(t, 1))
408412
require.NoError(t, err)
@@ -412,7 +416,7 @@ func TestResponseDespcriptionNil(t *testing.T) {
412416
From: interface{}(nil),
413417
To: "Tufin1",
414418
},
415-
d.PathsDiff.Modified["/api/{domain}/{project}/install-command"].OperationsDiff.Modified["GET"].ResponsesDiff.Modified["default"].DescriptionDiff)
419+
d.PathsDiff.Modified[installCommandPath].OperationsDiff.Modified["GET"].ResponsesDiff.Modified["default"].DescriptionDiff)
416420
}
417421

418422
func TestSchemaDiff_DeletedCallback(t *testing.T) {

0 commit comments

Comments
 (0)