Skip to content

Commit 062d778

Browse files
authored
Merge pull request #2936 from zmay2030/extension-diff
extension diff support
2 parents 5322415 + 7d67261 commit 062d778

File tree

5 files changed

+187
-11
lines changed

5 files changed

+187
-11
lines changed

diff/compatibility.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ func init() {
3434
DeletedTag: NonBreaking,
3535
DeletedConstraint: Breaking,
3636
AddedConstraint: NonBreaking,
37+
DeletedExtension: Warning,
38+
AddedExtension: Warning,
3739
},
3840
ForRequest: map[SpecChangeCode]Compatibility{
3941
AddedRequiredProperty: Breaking,
@@ -66,6 +68,8 @@ func init() {
6668
AddedExample: NonBreaking,
6769
DeletedExample: NonBreaking,
6870
ChangedCollectionFormat: Breaking,
71+
DeletedExtension: Warning,
72+
AddedExtension: Warning,
6973
},
7074
ForChange: map[SpecChangeCode]Compatibility{
7175
NoChangeDetected: NonBreaking,
@@ -90,6 +94,8 @@ func init() {
9094
RefTargetRenamed: NonBreaking,
9195
AddedDefinition: NonBreaking,
9296
DeletedDefinition: NonBreaking,
97+
DeletedExtension: Warning,
98+
AddedExtension: Warning,
9399
},
94100
}
95101
}

diff/difftypes.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ const (
113113
DeletedExample
114114
// ChangedCollectionFormat - A collectionFormat has been changed to a collectionFormat whose relative compatibility cannot be determined
115115
ChangedCollectionFormat
116+
// DeletedExtension deleted an extension
117+
DeletedExtension
118+
// AddedExtension added an extension
119+
AddedExtension
116120
)
117121

118122
var toLongStringSpecChangeCode = map[SpecChangeCode]string{
@@ -167,6 +171,8 @@ var toLongStringSpecChangeCode = map[SpecChangeCode]string{
167171
AddedExample: "Example value is added",
168172
DeletedExample: "Example value is removed",
169173
ChangedCollectionFormat: "Changed collection format",
174+
DeletedExtension: "Deleted Extension",
175+
AddedExtension: "Added Extension",
170176
}
171177

172178
var toStringSpecChangeCode = map[SpecChangeCode]string{
@@ -221,6 +227,8 @@ var toStringSpecChangeCode = map[SpecChangeCode]string{
221227
AddedExample: "AddedExample",
222228
DeletedExample: "DeletedExample",
223229
ChangedCollectionFormat: "ChangedCollectionFormat",
230+
DeletedExtension: "DeletedExtension",
231+
AddedExtension: "AddedExtension",
224232
}
225233

226234
var toIDSpecChangeCode = map[string]SpecChangeCode{}

diff/reporting.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func Compare(spec1, spec2 *spec.Swagger) (diffs SpecDifferences, err error) {
3131
type PathItemOp struct {
3232
ParentPathItem *spec.PathItem `json:"pathitem"`
3333
Operation *spec.Operation `json:"operation"`
34+
Extensions spec.Extensions `json:"extensions"`
3435
}
3536

3637
// URLMethod - combines url and method into a single keyed entity

diff/spec_analyser.go

Lines changed: 171 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ type SpecAnalyser struct {
3232
urlMethods2 URLMethods
3333
Definitions1 spec.Definitions
3434
Definitions2 spec.Definitions
35+
Info1 *spec.Info
36+
Info2 *spec.Info
3537
ReferencedDefinitions map[string]bool
3638

3739
schemasCompared map[string]struct{}
@@ -50,6 +52,8 @@ func (sd *SpecAnalyser) Analyse(spec1, spec2 *spec.Swagger) error {
5052
sd.schemasCompared = make(map[string]struct{})
5153
sd.Definitions1 = spec1.Definitions
5254
sd.Definitions2 = spec2.Definitions
55+
sd.Info1 = spec1.Info
56+
sd.Info2 = spec2.Info
5357
sd.urlMethods1 = getURLMethodsFor(spec1)
5458
sd.urlMethods2 = getURLMethodsFor(spec2)
5559

@@ -58,6 +62,7 @@ func (sd *SpecAnalyser) Analyse(spec1, spec2 *spec.Swagger) error {
5862
sd.analyseRequestParams()
5963
sd.analyseEndpointData()
6064
sd.analyseResponseParams()
65+
sd.analyseExtensions(spec1, spec2)
6166
sd.AnalyseDefinitions()
6267

6368
return nil
@@ -231,7 +236,6 @@ func (sd *SpecAnalyser) analyseResponseParams() {
231236
}
232237
// Added updated Response Codes
233238
for code2, op2Response := range op2Responses {
234-
235239
if op1Response, ok := op1Responses[code2]; ok {
236240
op1Headers := op1Response.ResponseProps.Headers
237241
headerRootNode := getNameOnlyDiffNode("Headers")
@@ -284,6 +288,163 @@ func (sd *SpecAnalyser) analyseResponseParams() {
284288
}
285289
}
286290

291+
func (sd *SpecAnalyser) analyseExtensions(spec1, spec2 *spec.Swagger) {
292+
// root
293+
specLoc := DifferenceLocation{Node: &Node{Field: "Spec"}}
294+
sd.checkAddedExtensions(spec1.Extensions, spec2.Extensions, specLoc, "")
295+
sd.checkDeletedExtensions(spec1.Extensions, spec2.Extensions, specLoc, "")
296+
297+
sd.analyzeInfoExtensions()
298+
sd.analyzeTagExtensions(spec1, spec2)
299+
sd.analyzeSecurityDefinitionExtensions(spec1, spec2)
300+
301+
sd.analyzeOperationExtensions()
302+
}
303+
304+
func (sd *SpecAnalyser) analyzeOperationExtensions() {
305+
for urlMethod, op2 := range sd.urlMethods2 {
306+
pathAndMethodLoc := DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method}
307+
if op1, ok := sd.urlMethods1[urlMethod]; ok {
308+
sd.checkAddedExtensions(op1.Extensions, op2.Extensions, DifferenceLocation{URL: urlMethod.Path}, "")
309+
sd.checkAddedExtensions(op1.Operation.Responses.Extensions, op2.Operation.Responses.Extensions, pathAndMethodLoc, "Responses")
310+
sd.checkAddedExtensions(op1.Operation.Extensions, op2.Operation.Extensions, pathAndMethodLoc, "")
311+
312+
for code, resp := range op1.Operation.Responses.StatusCodeResponses {
313+
for hdr, h := range resp.Headers {
314+
op2StatusCode, ok := op2.Operation.Responses.StatusCodeResponses[code]
315+
if ok {
316+
if _, ok = op2StatusCode.Headers[hdr]; ok {
317+
sd.checkAddedExtensions(h.Extensions, op2StatusCode.Headers[hdr].Extensions, DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method, Node: getNameOnlyDiffNode("Headers")}, hdr)
318+
}
319+
}
320+
}
321+
322+
resp2 := op2.Operation.Responses.StatusCodeResponses[code]
323+
sd.analyzeSchemaExtensions(resp.Schema, resp2.Schema, code, urlMethod)
324+
}
325+
326+
}
327+
}
328+
329+
for urlMethod, op1 := range sd.urlMethods1 {
330+
pathAndMethodLoc := DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method}
331+
if op2, ok := sd.urlMethods2[urlMethod]; ok {
332+
sd.checkDeletedExtensions(op1.Extensions, op2.Extensions, DifferenceLocation{URL: urlMethod.Path}, "")
333+
sd.checkDeletedExtensions(op1.Operation.Responses.Extensions, op2.Operation.Responses.Extensions, pathAndMethodLoc, "Responses")
334+
sd.checkDeletedExtensions(op1.Operation.Extensions, op2.Operation.Extensions, pathAndMethodLoc, "")
335+
for code, resp := range op1.Operation.Responses.StatusCodeResponses {
336+
for hdr, h := range resp.Headers {
337+
op2StatusCode, ok := op2.Operation.Responses.StatusCodeResponses[code]
338+
if ok {
339+
if _, ok = op2StatusCode.Headers[hdr]; ok {
340+
sd.checkDeletedExtensions(h.Extensions, op2StatusCode.Headers[hdr].Extensions, DifferenceLocation{URL: urlMethod.Path, Method: urlMethod.Method, Node: getNameOnlyDiffNode("Headers")}, hdr)
341+
}
342+
}
343+
}
344+
}
345+
}
346+
}
347+
}
348+
349+
func (sd *SpecAnalyser) analyzeSecurityDefinitionExtensions(spec1 *spec.Swagger, spec2 *spec.Swagger) {
350+
securityDefLoc := DifferenceLocation{Node: &Node{Field: "Security Definitions"}}
351+
for key, securityDef := range spec1.SecurityDefinitions {
352+
if securityDef2, ok := spec2.SecurityDefinitions[key]; ok {
353+
sd.checkAddedExtensions(securityDef.Extensions, securityDef2.Extensions, securityDefLoc, "")
354+
}
355+
}
356+
357+
for key, securityDef := range spec2.SecurityDefinitions {
358+
if securityDef1, ok := spec1.SecurityDefinitions[key]; ok {
359+
sd.checkDeletedExtensions(securityDef1.Extensions, securityDef.Extensions, securityDefLoc, "")
360+
}
361+
}
362+
}
363+
364+
func (sd *SpecAnalyser) analyzeSchemaExtensions(schema1, schema2 *spec.Schema, code int, urlMethod URLMethod) {
365+
if schema1 != nil && schema2 != nil {
366+
diffLoc := DifferenceLocation{Response: code, URL: urlMethod.Path, Method: urlMethod.Method, Node: getSchemaDiffNode("Body", schema2)}
367+
sd.checkAddedExtensions(schema1.Extensions, schema2.Extensions, diffLoc, "")
368+
sd.checkDeletedExtensions(schema1.Extensions, schema2.Extensions, diffLoc, "")
369+
if schema1.Items != nil && schema2.Items != nil {
370+
sd.analyzeSchemaExtensions(schema1.Items.Schema, schema2.Items.Schema, code, urlMethod)
371+
for i := range schema1.Items.Schemas {
372+
s1 := schema1.Items.Schemas[i]
373+
for j := range schema2.Items.Schemas {
374+
s2 := schema2.Items.Schemas[j]
375+
sd.analyzeSchemaExtensions(&s1, &s2, code, urlMethod)
376+
}
377+
}
378+
}
379+
}
380+
}
381+
382+
func (sd *SpecAnalyser) analyzeInfoExtensions() {
383+
if sd.Info1 != nil && sd.Info2 != nil {
384+
diffLocation := DifferenceLocation{Node: &Node{Field: "Spec Info"}}
385+
sd.checkAddedExtensions(sd.Info1.Extensions, sd.Info2.Extensions, diffLocation, "")
386+
sd.checkDeletedExtensions(sd.Info1.Extensions, sd.Info2.Extensions, diffLocation, "")
387+
if sd.Info1.Contact != nil && sd.Info2.Contact != nil {
388+
diffLocation = DifferenceLocation{Node: &Node{Field: "Spec Info.Contact"}}
389+
sd.checkAddedExtensions(sd.Info1.Contact.Extensions, sd.Info2.Contact.Extensions, diffLocation, "")
390+
sd.checkDeletedExtensions(sd.Info1.Contact.Extensions, sd.Info2.Contact.Extensions, diffLocation, "")
391+
}
392+
if sd.Info1.License != nil && sd.Info2.License != nil {
393+
diffLocation = DifferenceLocation{Node: &Node{Field: "Spec Info.License"}}
394+
sd.checkAddedExtensions(sd.Info1.License.Extensions, sd.Info2.License.Extensions, diffLocation, "")
395+
sd.checkDeletedExtensions(sd.Info1.License.Extensions, sd.Info2.License.Extensions, diffLocation, "")
396+
}
397+
}
398+
}
399+
400+
func (sd *SpecAnalyser) analyzeTagExtensions(spec1 *spec.Swagger, spec2 *spec.Swagger) {
401+
diffLocation := DifferenceLocation{Node: &Node{Field: "Spec Tags"}}
402+
for _, spec2Tag := range spec2.Tags {
403+
for _, spec1Tag := range spec1.Tags {
404+
if spec2Tag.Name == spec1Tag.Name {
405+
sd.checkAddedExtensions(spec1Tag.Extensions, spec2Tag.Extensions, diffLocation, "")
406+
}
407+
}
408+
}
409+
for _, spec1Tag := range spec1.Tags {
410+
for _, spec2Tag := range spec2.Tags {
411+
if spec1Tag.Name == spec2Tag.Name {
412+
sd.checkDeletedExtensions(spec1Tag.Extensions, spec2Tag.Extensions, diffLocation, "")
413+
}
414+
}
415+
}
416+
}
417+
418+
func (sd *SpecAnalyser) checkAddedExtensions(extensions1 spec.Extensions, extensions2 spec.Extensions, diffLocation DifferenceLocation, fieldPrefix string) {
419+
for extKey := range extensions2 {
420+
if _, ok := extensions1[extKey]; !ok {
421+
if fieldPrefix != "" {
422+
extKey = fmt.Sprintf("%s.%s", fieldPrefix, extKey)
423+
}
424+
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
425+
DifferenceLocation: diffLocation.AddNode(&Node{Field: extKey}),
426+
Code: AddedExtension,
427+
Compatibility: Warning, // this could potentially be a breaking change
428+
})
429+
}
430+
}
431+
}
432+
433+
func (sd *SpecAnalyser) checkDeletedExtensions(extensions1 spec.Extensions, extensions2 spec.Extensions, diffLocation DifferenceLocation, fieldPrefix string) {
434+
for extKey := range extensions1 {
435+
if _, ok := extensions2[extKey]; !ok {
436+
if fieldPrefix != "" {
437+
extKey = fmt.Sprintf("%s.%s", fieldPrefix, extKey)
438+
}
439+
sd.Diffs = sd.Diffs.addDiff(SpecDifference{
440+
DifferenceLocation: diffLocation.AddNode(&Node{Field: extKey}),
441+
Code: DeletedExtension,
442+
Compatibility: Warning, // this could potentially be a breaking change
443+
})
444+
}
445+
}
446+
}
447+
287448
func addTypeDiff(diffs []TypeDiff, diff TypeDiff) []TypeDiff {
288449
if diff.Change != NoChangeDetected {
289450
diffs = append(diffs, diff)
@@ -370,9 +531,7 @@ func (sd *SpecAnalyser) compareParams(urlMethod URLMethod, location string, name
370531
sd.addDiffs(childLocation, diffs)
371532
}
372533

373-
if &param1.SimpleSchema != nil && &param2.SimpleSchema != nil {
374-
sd.compareSimpleSchema(childLocation, &param1.SimpleSchema, &param2.SimpleSchema)
375-
}
534+
sd.compareSimpleSchema(childLocation, &param1.SimpleSchema, &param2.SimpleSchema)
376535
}
377536

378537
func (sd *SpecAnalyser) addTypeDiff(location DifferenceLocation, diff *TypeDiff) {
@@ -483,21 +642,23 @@ func (sd *SpecAnalyser) compareSimpleSchema(location DifferenceLocation, schema1
483642
}
484643

485644
if schema1.Default != schema2.Default {
486-
if schema1.Default == nil && schema2.Default != nil {
645+
switch {
646+
case schema1.Default == nil && schema2.Default != nil:
487647
sd.addDiffs(location, addTypeDiff([]TypeDiff{}, TypeDiff{Change: AddedDefault, FromType: getSchemaTypeStr(schema1), ToType: getSchemaTypeStr(schema2)}))
488-
} else if schema1.Default != nil && schema2.Default == nil {
648+
case schema1.Default != nil && schema2.Default == nil:
489649
sd.addDiffs(location, addTypeDiff([]TypeDiff{}, TypeDiff{Change: DeletedDefault, FromType: getSchemaTypeStr(schema1), ToType: getSchemaTypeStr(schema2)}))
490-
} else {
650+
default:
491651
sd.addDiffs(location, addTypeDiff([]TypeDiff{}, TypeDiff{Change: ChangedDefault, FromType: getSchemaTypeStr(schema1), ToType: getSchemaTypeStr(schema2)}))
492652
}
493653
}
494654

495655
if schema1.Example != schema2.Example {
496-
if schema1.Example == nil && schema2.Example != nil {
656+
switch {
657+
case schema1.Example == nil && schema2.Example != nil:
497658
sd.addDiffs(location, addTypeDiff([]TypeDiff{}, TypeDiff{Change: AddedExample, FromType: getSchemaTypeStr(schema1), ToType: getSchemaTypeStr(schema2)}))
498-
} else if schema1.Example != nil && schema2.Example == nil {
659+
case schema1.Example != nil && schema2.Example == nil:
499660
sd.addDiffs(location, addTypeDiff([]TypeDiff{}, TypeDiff{Change: DeletedExample, FromType: getSchemaTypeStr(schema1), ToType: getSchemaTypeStr(schema2)}))
500-
} else {
661+
default:
501662
sd.addDiffs(location, addTypeDiff([]TypeDiff{}, TypeDiff{Change: ChangedExample, FromType: getSchemaTypeStr(schema1), ToType: getSchemaTypeStr(schema2)}))
502663
}
503664
}

diff/type_adapters.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func getURLMethodsFor(spec *spec.Swagger) URLMethods {
107107
eachPath := eachPath
108108
opsMap := toMap(&eachPath)
109109
for method, op := range opsMap {
110-
returnURLMethods[URLMethod{url, method}] = &PathItemOp{&eachPath, op}
110+
returnURLMethods[URLMethod{url, method}] = &PathItemOp{&eachPath, op, eachPath.Extensions}
111111
}
112112
}
113113
return returnURLMethods

0 commit comments

Comments
 (0)