@@ -22,15 +22,17 @@ import (
22
22
"github.com/getkin/kin-openapi/openapi3"
23
23
"github.com/mongodb/openapi/tools/cli/internal/openapi/errors"
24
24
"github.com/tufin/oasdiff/diff"
25
+
25
26
"github.com/tufin/oasdiff/load"
26
27
)
27
28
28
29
type OasDiff struct {
29
- base * load.SpecInfo
30
- external * load.SpecInfo
31
- config * diff.Config
32
- result * OasDiffResult
33
- parser Parser
30
+ base * load.SpecInfo
31
+ external * load.SpecInfo
32
+ config * diff.Config
33
+ diffGetter Differ
34
+ result * OasDiffResult
35
+ parser Parser
34
36
}
35
37
36
38
func (o OasDiff ) mergeSpecIntoBase () (* load.SpecInfo , error ) {
@@ -69,16 +71,17 @@ func (o OasDiff) mergePaths() error {
69
71
return nil
70
72
}
71
73
72
- for k , v := range pathsToMerge .Map () {
73
- if ok := basePaths .Value (k ); ok == nil {
74
- basePaths .Set (k , removeExternalRefs (v ))
74
+ for path , externalPathData := range pathsToMerge .Map () {
75
+ // Tries to find if the path already exists or not
76
+ if originalPathData := basePaths .Value (path ); originalPathData == nil {
77
+ basePaths .Set (path , removeExternalRefs (externalPathData ))
75
78
} else {
76
- return errors. PathConflictError {
77
- Entry : k ,
79
+ if err := o . handlePathConflict ( originalPathData , path ); err != nil {
80
+ return err
78
81
}
82
+ basePaths .Set (path , removeExternalRefs (externalPathData ))
79
83
}
80
84
}
81
-
82
85
o .base .Spec .Paths = basePaths
83
86
return nil
84
87
}
@@ -118,6 +121,71 @@ func removeExternalRefs(path *openapi3.PathItem) *openapi3.PathItem {
118
121
return path
119
122
}
120
123
124
+ // handlePathConflict handles the path conflict by checking if the conflict should be skipped or not.
125
+ func (o OasDiff ) handlePathConflict (basePath * openapi3.PathItem , basePathName string ) error {
126
+ if ! o .shouldSkipPathConflict (basePath , basePathName ) {
127
+ return errors.PathConflictError {
128
+ Entry : basePathName ,
129
+ }
130
+ }
131
+
132
+ var pathsAreIdentical bool
133
+ var err error
134
+ if pathsAreIdentical , err = o .arePathsIdenticalWithExcludeExtensions (basePathName ); err != nil {
135
+ return err
136
+ }
137
+
138
+ log .Printf ("Skipping conflict for path: %s, pathsAreIdentical: %v" , basePathName , pathsAreIdentical )
139
+ if pathsAreIdentical {
140
+ return nil
141
+ }
142
+
143
+ // allowDocsDiff = true not supported
144
+ if allOperationsAllowDocsDiff (basePath ) {
145
+ return errors.AllowDocsDiffNotSupportedError {
146
+ Entry : basePathName ,
147
+ }
148
+ }
149
+
150
+ exclude := []string {"extensions" }
151
+ customConfig := diff .NewConfig ().WithExcludeElements (exclude )
152
+ d , err := o .GetDiffWithConfig (o .base , o .external , customConfig )
153
+ if err != nil {
154
+ return err
155
+ }
156
+
157
+ return errors.PathDocsDiffConflictError {
158
+ Entry : basePathName ,
159
+ Diff : d .Report ,
160
+ }
161
+ }
162
+
163
+ // shouldSkipConflict checks if the conflict should be skipped.
164
+ // The method goes through each path operation and performs the following checks:
165
+ // 1. Validates if both paths have same operations, if not, then it returns false.
166
+ // 2. If both paths have the same operations, then it checks if there is an x-xgen-soa-migration annotation.
167
+ // If there is no annotation, then it returns false.
168
+ func (o OasDiff ) shouldSkipPathConflict (basePath * openapi3.PathItem , basePathName string ) bool {
169
+ var pathsDiff * diff.PathsDiff
170
+ if o .result != nil && o .result .Report != nil && o .result .Report .PathsDiff != nil {
171
+ pathsDiff = o .result .Report .PathsDiff
172
+ }
173
+
174
+ if pathsDiff != nil && pathsDiff .Modified != nil && pathsDiff .Modified [basePathName ] != nil {
175
+ if ok := pathsDiff .Modified [basePathName ].OperationsDiff .Added ; ! ok .Empty () {
176
+ return false
177
+ }
178
+
179
+ if ok := pathsDiff .Modified [basePathName ].OperationsDiff .Deleted ; ! ok .Empty () {
180
+ return false
181
+ }
182
+ }
183
+
184
+ // now check if there is an x-xgen-soa-migration annotation in any of the operations, but if any of the operations
185
+ // doesn't have, then we should not skip the conflict
186
+ return allOperationsHaveExtension (basePath , basePathName , xgenSoaMigration )
187
+ }
188
+
121
189
// updateExternalRefResponses updates the external references of OASes to remove the reference to openapi-mms.json
122
190
// in the Responses.
123
191
// A Response can have an external ref in Response.Ref or in its content (Response.Content.Schema.Ref)
@@ -375,6 +443,29 @@ func (o OasDiff) areSchemaIdentical(name string) bool {
375
443
return ! ok
376
444
}
377
445
446
+ // arePathsIdenticalWithExcludeExtensions checks if the paths are identical excluding extension diffs across operations (e.g. x-xgen-soa-migration).
447
+ func (o OasDiff ) arePathsIdenticalWithExcludeExtensions (name string ) (bool , error ) {
448
+ // If the diff only has extensions diff, then we consider the paths to be identical
449
+ customConfig := diff .NewConfig ().WithExcludeElements ([]string {"extensions" })
450
+ result , err := o .GetDiffWithConfig (o .base , o .external , customConfig )
451
+ if err != nil {
452
+ return false , err
453
+ }
454
+
455
+ d := result .Report
456
+ if d .Empty () || d .PathsDiff .Empty () {
457
+ return true , nil
458
+ }
459
+ v , ok := d .PathsDiff .Modified [name ]
460
+ if ok {
461
+ if v .Empty () {
462
+ return true , nil
463
+ }
464
+ }
465
+
466
+ return ! ok , nil
467
+ }
468
+
378
469
type ByName []* openapi3.Tag
379
470
380
471
func (a ByName ) Len () int { return len (a ) }
0 commit comments