@@ -22,15 +22,17 @@ import (
2222 "github.com/getkin/kin-openapi/openapi3"
2323 "github.com/mongodb/openapi/tools/cli/internal/openapi/errors"
2424 "github.com/tufin/oasdiff/diff"
25+
2526 "github.com/tufin/oasdiff/load"
2627)
2728
2829type 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
3436}
3537
3638func (o OasDiff ) mergeSpecIntoBase () (* load.SpecInfo , error ) {
@@ -69,16 +71,17 @@ func (o OasDiff) mergePaths() error {
6971 return nil
7072 }
7173
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 ))
7578 } else {
76- return errors. PathConflictError {
77- Entry : k ,
79+ if err := o . handlePathConflict ( originalPathData , path ); err != nil {
80+ return err
7881 }
82+ basePaths .Set (path , removeExternalRefs (externalPathData ))
7983 }
8084 }
81-
8285 o .base .Spec .Paths = basePaths
8386 return nil
8487}
@@ -118,6 +121,71 @@ func removeExternalRefs(path *openapi3.PathItem) *openapi3.PathItem {
118121 return path
119122}
120123
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+
121189// updateExternalRefResponses updates the external references of OASes to remove the reference to openapi-mms.json
122190// in the Responses.
123191// 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 {
375443 return ! ok
376444}
377445
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+
378469type ByName []* openapi3.Tag
379470
380471func (a ByName ) Len () int { return len (a ) }
0 commit comments