Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use real root filename as theoretical root yaml file #322

Merged
merged 6 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bundler/bundler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func TestBundleDocument_DigitalOcean(t *testing.T) {
digi, _ := os.ReadFile(spec)

doc, err := libopenapi.NewDocumentWithConfiguration([]byte(digi), &datamodel.DocumentConfiguration{
SpecFilePath: spec,
BasePath: tmp + "/specification",
ExtractRefsSequentially: true,
Logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Expand Down
3 changes: 3 additions & 0 deletions datamodel/document_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type DocumentConfiguration struct {
// To avoid sucking in all the files, set the FileFilter to a list of specific files to be included.
BasePath string // set the Base Path for resolving relative references if the spec is exploded.

// SpecFilePath is the name of the root specification file (usually named "openapi.yaml").
SpecFilePath string

// FileFilter is a list of specific files to be included by the rolodex when looking up references. If this value
// is set, then only these specific files will be included. If this value is not set, then all files will be included.
FileFilter []string
Expand Down
1 change: 1 addition & 0 deletions datamodel/low/v3/create_document.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
idxConfig.AvoidCircularReferenceCheck = true
idxConfig.BaseURL = config.BaseURL
idxConfig.BasePath = config.BasePath
idxConfig.SpecFilePath = config.SpecFilePath
idxConfig.Logger = config.Logger
extract := config.ExtractRefsSequentially
idxConfig.ExtractRefsSequentially = extract
Expand Down
15 changes: 15 additions & 0 deletions index/index_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log/slog"
"net/http"
"net/url"
"path/filepath"
"sync"

"github.com/pb33f/libopenapi/datamodel"
Expand Down Expand Up @@ -87,6 +88,9 @@ type SpecIndexConfig struct {
// If resolving locally, the BasePath will be the root from which relative references will be resolved from
BasePath string // set the Base Path for resolving relative references if the spec is exploded.

// SpecFilePath is the name of the root specification file (usually named "openapi.yaml").
SpecFilePath string

// In an earlier version of libopenapi (pre 0.6.0) the index would automatically resolve all references
// They could have been local, or they could have been remote. This was a problem because it meant
// There was a potential for a remote exploit if a remote reference was malicious. There aren't any known
Expand Down Expand Up @@ -151,6 +155,17 @@ type SpecIndexConfig struct {
uri []string
}

// SetTheoreticalRoot sets the spec file paths to a theoretical file path
daveshanley marked this conversation as resolved.
Show resolved Hide resolved
func (s *SpecIndexConfig) SetTheoreticalRoot() {
s.SpecFilePath = filepath.Join(s.BasePath, theoreticalRoot)

basePath := s.BasePath
if !filepath.IsAbs(basePath) {
basePath, _ = filepath.Abs(basePath)
}
s.SpecAbsolutePath = filepath.Join(basePath, theoreticalRoot)
}

// CreateOpenAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
// AllowFileLookup set to true. This is the default behaviour of the index in previous versions of libopenapi. (pre 0.6.0)
//
Expand Down
35 changes: 33 additions & 2 deletions index/rolodex.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"io"
"io/fs"
"log/slog"
"maps"
"math"
"os"
"path/filepath"
Expand Down Expand Up @@ -302,7 +303,7 @@ func (r *Rolodex) IndexTheRolodex() error {
// indexed and built every supporting file, we can build the root index (our entry point)
if r.rootNode != nil {

// if there is a base path, then we need to set the root spec config to point to a theoretical root.yaml
// if there is a base path but no SpecFilePath, then we need to set the root spec config to point to a theoretical root.yaml
// which does not exist, but is used to formulate the absolute path to root references correctly.
if r.indexConfig.BasePath != "" && r.indexConfig.BaseURL == nil {

Expand All @@ -312,7 +313,11 @@ func (r *Rolodex) IndexTheRolodex() error {
}

if len(r.localFS) > 0 || len(r.remoteFS) > 0 {
r.indexConfig.SpecAbsolutePath = filepath.Join(basePath, "root.yaml")
if r.indexConfig.SpecFilePath != "" {
r.indexConfig.SpecAbsolutePath = filepath.Join(basePath, filepath.Base(r.indexConfig.SpecFilePath))
} else {
r.indexConfig.SetTheoreticalRoot()
}
}
}

Expand Down Expand Up @@ -425,6 +430,32 @@ func (r *Rolodex) BuildIndexes() {
r.manualBuilt = true
}

// GetAllReferences returns all references found in the root and all other indices
func (r *Rolodex) GetAllReferences() map[string]*Reference {
allRefs := make(map[string]*Reference)
for _, idx := range append(r.GetIndexes(), r.GetRootIndex()) {
daveshanley marked this conversation as resolved.
Show resolved Hide resolved
if idx == nil {
continue
}
refs := idx.GetAllReferences()
maps.Copy(allRefs, refs)
}
return allRefs
}

// GetAllMappedReferences returns all mapped references found in the root and all other indices
func (r *Rolodex) GetAllMappedReferences() map[string]*Reference {
mappedRefs := make(map[string]*Reference)
for _, idx := range append(r.GetIndexes(), r.GetRootIndex()) {
if idx == nil {
continue
}
refs := idx.GetMappedReferences()
maps.Copy(mappedRefs, refs)
}
return mappedRefs
}

// Open opens a file in the rolodex, and returns a RolodexFile.
func (r *Rolodex) Open(location string) (RolodexFile, error) {

Expand Down
13 changes: 12 additions & 1 deletion index/rolodex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
func TestRolodex_NewRolodex(t *testing.T) {
c := CreateOpenAPIIndexConfig()
rolo := NewRolodex(c)
assert.Len(t, rolo.GetAllReferences(), 0)
assert.Len(t, rolo.GetAllMappedReferences(), 0)
assert.NotNil(t, rolo)
assert.NotNil(t, rolo.indexConfig)
assert.Nil(t, rolo.GetIgnoredCircularReferences())
Expand Down Expand Up @@ -1516,18 +1518,27 @@ func TestRolodex_SimpleTest_OneDoc(t *testing.T) {
}

cf := CreateOpenAPIIndexConfig()
cf.SpecFilePath = filepath.Join(baseDir, "doc1.yaml")
daveshanley marked this conversation as resolved.
Show resolved Hide resolved
cf.BasePath = baseDir
cf.IgnoreArrayCircularReferences = true
cf.IgnorePolymorphicCircularReferences = true

rolo := NewRolodex(cf)
rolo.AddLocalFS(baseDir, fileFS)

rootBytes, err := os.ReadFile(cf.SpecFilePath)
assert.NoError(t, err)
var rootNode yaml.Node
_ = yaml.Unmarshal(rootBytes, &rootNode)
rolo.SetRootNode(&rootNode)

err = rolo.IndexTheRolodex()

//assert.NotZero(t, rolo.GetIndexingDuration()) comes back as 0 on windows.
assert.Nil(t, rolo.GetRootIndex())
assert.NotNil(t, rolo.GetRootIndex())
assert.Len(t, rolo.GetIndexes(), 10)
assert.Len(t, rolo.GetAllReferences(), 7)
assert.Len(t, rolo.GetAllMappedReferences(), 7)

assert.NoError(t, err)
assert.Len(t, rolo.indexes, 10)
Expand Down
2 changes: 1 addition & 1 deletion index/search_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex
if strings.Contains(roloLookup, "#") {
roloLookup = strings.Split(roloLookup, "#")[0]
}
if filepath.Base(roloLookup) == "root.yaml" {
if filepath.Base(roloLookup) == index.GetSpecFileName() {
return nil, index, ctx
}
rFile, err := index.rolodex.Open(roloLookup)
Expand Down
11 changes: 11 additions & 0 deletions index/spec_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import (
"gopkg.in/yaml.v3"
)

const (
theoreticalRoot = "root.yaml"
)

// NewSpecIndexWithConfig will create a new index of an OpenAPI or Swagger spec. It uses the same logic as NewSpecIndex
// except it sets a base URL for resolving relative references, except it also allows for granular control over
// how the index is set up.
Expand Down Expand Up @@ -152,6 +156,13 @@ func (index *SpecIndex) GetRolodex() *Rolodex {
return index.rolodex
}

func (index *SpecIndex) GetSpecFileName() string {
daveshanley marked this conversation as resolved.
Show resolved Hide resolved
if index == nil || index.rolodex == nil || index.rolodex.indexConfig == nil {
return theoreticalRoot
}
return index.rolodex.indexConfig.SpecFilePath
}

// GetGlobalTagsNode returns document root tags node.
func (index *SpecIndex) GetGlobalTagsNode() *yaml.Node {
return index.tagsNode
Expand Down
Loading