diff --git a/tools/cli/go.mod b/tools/cli/go.mod index 6ff94b0a2..099c2949c 100644 --- a/tools/cli/go.mod +++ b/tools/cli/go.mod @@ -3,12 +3,13 @@ module mongodb/openapi/tools/cli go 1.22.1 require ( + github.com/getkin/kin-openapi v0.120.0 github.com/spf13/cobra v1.8.0 github.com/tufin/oasdiff v1.9.5 ) require ( - github.com/getkin/kin-openapi v0.120.0 // indirect + cloud.google.com/go v0.110.10 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/tools/cli/go.sum b/tools/cli/go.sum index d89a2c111..3fcc8a595 100644 --- a/tools/cli/go.sum +++ b/tools/cli/go.sum @@ -1,3 +1,7 @@ +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= +github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -9,6 +13,8 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= @@ -42,6 +48,8 @@ github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0 github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/tools/cli/internal/openapi/errors/pathConflictError.go b/tools/cli/internal/openapi/errors/pathConflictError.go new file mode 100644 index 000000000..59fd5b8ae --- /dev/null +++ b/tools/cli/internal/openapi/errors/pathConflictError.go @@ -0,0 +1,11 @@ +package errors + +import "fmt" + +type PathConflictError struct { + Entry string +} + +func (e PathConflictError) Error() string { + return fmt.Sprintf("there was a conflict with the Path: %s", e.Entry) +} diff --git a/tools/cli/internal/openapi/oasdiff.go b/tools/cli/internal/openapi/oasdiff.go new file mode 100644 index 000000000..b8ebf258a --- /dev/null +++ b/tools/cli/internal/openapi/oasdiff.go @@ -0,0 +1,74 @@ +package openapi + +import ( + "fmt" + "mongodb/openapi/tools/cli/internal/openapi/errors" + "os" + + "github.com/tufin/oasdiff/diff" + "github.com/tufin/oasdiff/load" +) + +type OasDiff struct { + base *load.SpecInfo + external *load.SpecInfo + config *diff.Config + specDiff *diff.Diff + parser Parser +} + +func (o *OasDiff) MergeOpenAPISpecs(paths []string) (*load.SpecInfo, error) { + for _, p := range paths { + spec, err := o.parser.CreateOpenAPISpecFromPath(p) + if err != nil { + return nil, err + } + + o.config = &diff.Config{ + IncludePathParams: true, + } + + specDiff, err := diff.Get(o.config, o.base.Spec, spec.Spec) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "error in calculating the diff of the specs: %s", err) + return nil, err + } + + o.specDiff = specDiff + o.external = spec + err = o.mergeSpecIntoBase() + if err != nil { + return nil, err + } + } + + return o.base, nil +} + +func (o OasDiff) mergeSpecIntoBase() error { + return o.mergePaths() +} + +func (o OasDiff) mergePaths() error { + pathsToMerge := o.external.Spec.Paths + basePaths := o.base.Spec.Paths + for k, v := range pathsToMerge { + if _, ok := basePaths[k]; !ok { + basePaths[k] = v + } else { + return errors.PathConflictError{ + Entry: k, + } + } + } + + o.base.Spec.Paths = basePaths + return nil +} + +func NewOasDiff(base *load.SpecInfo) *OasDiff { + return &OasDiff{ + base: base, + parser: NewOpenAPI3(), + } +} diff --git a/tools/cli/internal/openapi/openapi.go b/tools/cli/internal/openapi/openapi.go new file mode 100644 index 000000000..5bdb95939 --- /dev/null +++ b/tools/cli/internal/openapi/openapi.go @@ -0,0 +1,13 @@ +package openapi + +import ( + "github.com/tufin/oasdiff/load" +) + +type Merger interface { + MergeOpenAPISpecs([]string) (*load.SpecInfo, error) +} + +type Parser interface { + CreateOpenAPISpecFromPath(string) (*load.SpecInfo, error) +} diff --git a/tools/cli/internal/openapi/openapi3.go b/tools/cli/internal/openapi/openapi3.go new file mode 100644 index 000000000..3f5bef023 --- /dev/null +++ b/tools/cli/internal/openapi/openapi3.go @@ -0,0 +1,31 @@ +package openapi + +import ( + "github.com/getkin/kin-openapi/openapi3" + "github.com/tufin/oasdiff/load" +) + +type OpenAPI3 struct { + IsExternalRefsAllowed bool + CircularReferenceCounter int +} + +func (o *OpenAPI3) CreateOpenAPISpecFromPath(path string) (*load.SpecInfo, error) { + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + openapi3.CircularReferenceCounter = 10 + + spec, err := load.LoadSpecInfo(loader, load.NewSource(path)) + if err != nil { + return nil, err + } + + return spec, nil +} + +func NewOpenAPI3() *OpenAPI3 { + return &OpenAPI3{ + IsExternalRefsAllowed: true, + CircularReferenceCounter: 10, + } +}