working through rolodex design and using it externally via vacuum

this is some complex and messy work.

Signed-off-by: quobix <dave@quobix.com>
This commit is contained in:
quobix
2023-10-15 12:34:54 -04:00
parent 511843e4df
commit 8b795c6321
6 changed files with 93 additions and 28 deletions

View File

@@ -36,6 +36,9 @@ type Document interface {
// GetVersion will return the exact version of the OpenAPI specification set for the document. // GetVersion will return the exact version of the OpenAPI specification set for the document.
GetVersion() string GetVersion() string
// GetRolodex will return the Rolodex instance that was used to load the document.
GetRolodex() *index.Rolodex
// GetSpecInfo will return the *datamodel.SpecInfo instance that contains all specification information. // GetSpecInfo will return the *datamodel.SpecInfo instance that contains all specification information.
GetSpecInfo() *datamodel.SpecInfo GetSpecInfo() *datamodel.SpecInfo
@@ -102,6 +105,7 @@ type Document interface {
} }
type document struct { type document struct {
rolodex *index.Rolodex
version string version string
info *datamodel.SpecInfo info *datamodel.SpecInfo
config *datamodel.DocumentConfiguration config *datamodel.DocumentConfiguration
@@ -161,6 +165,10 @@ func NewDocumentWithConfiguration(specByteArray []byte, configuration *datamodel
return d, err return d, err
} }
func (d *document) GetRolodex() *index.Rolodex {
return d.rolodex
}
func (d *document) GetVersion() string { func (d *document) GetVersion() string {
return d.version return d.version
} }
@@ -281,15 +289,15 @@ func (d *document) BuildV3Model() (*DocumentModel[v3high.Document], []error) {
if d.highOpenAPI3Model != nil { if d.highOpenAPI3Model != nil {
return d.highOpenAPI3Model, nil return d.highOpenAPI3Model, nil
} }
var errors []error var errs []error
if d.info == nil { if d.info == nil {
errors = append(errors, fmt.Errorf("unable to build document, no specification has been loaded")) errs = append(errs, fmt.Errorf("unable to build document, no specification has been loaded"))
return nil, errors return nil, errs
} }
if d.info.SpecFormat != datamodel.OAS3 { if d.info.SpecFormat != datamodel.OAS3 {
errors = append(errors, fmt.Errorf("unable to build openapi document, "+ errs = append(errs, fmt.Errorf("unable to build openapi document, "+
"supplied spec is a different version (%v). Try 'BuildV2Model()'", d.info.SpecFormat)) "supplied spec is a different version (%v). Try 'BuildV2Model()'", d.info.SpecFormat))
return nil, errors return nil, errs
} }
var lowDoc *v3low.Document var lowDoc *v3low.Document
@@ -300,24 +308,26 @@ func (d *document) BuildV3Model() (*DocumentModel[v3high.Document], []error) {
} }
} }
lowDoc, errors = v3low.CreateDocumentFromConfig(d.info, d.config) var docErr error
lowDoc, docErr = v3low.CreateDocumentFromConfig(d.info, d.config)
d.rolodex = lowDoc.Rolodex
// Do not short-circuit on circular reference errors, so the client // Do not short-circuit on circular reference errors, so the client
// has the option of ignoring them. // has the option of ignoring them.
for _, err := range errors { for _, err := range utils.UnwrapErrors(docErr) {
if refErr, ok := err.(*index.ResolvingError); ok { var refErr *index.ResolvingError
if errors.As(err, &refErr) {
if refErr.CircularReference == nil { if refErr.CircularReference == nil {
return nil, errors return nil, errs
} }
} else {
return nil, errors
} }
} }
highDoc := v3high.NewDocument(lowDoc) highDoc := v3high.NewDocument(lowDoc)
d.highOpenAPI3Model = &DocumentModel[v3high.Document]{ d.highOpenAPI3Model = &DocumentModel[v3high.Document]{
Model: *highDoc, Model: *highDoc,
Index: lowDoc.Index, Index: lowDoc.Index,
} }
return d.highOpenAPI3Model, errors return d.highOpenAPI3Model, errs
} }
// CompareDocuments will accept a left and right Document implementing struct, build a model for the correct // CompareDocuments will accept a left and right Document implementing struct, build a model for the correct

View File

@@ -822,6 +822,7 @@ components:
assert.Len(t, errs, 0) assert.Len(t, errs, 0)
assert.Len(t, m.Index.GetCircularReferences(), 0) assert.Len(t, m.Index.GetCircularReferences(), 0)
assert.Len(t, m.Index.GetResolver().GetIgnoredCircularPolyReferences(), 1)
} }
@@ -856,5 +857,6 @@ components:
assert.Len(t, errs, 0) assert.Len(t, errs, 0)
assert.Len(t, m.Index.GetCircularReferences(), 0) assert.Len(t, m.Index.GetCircularReferences(), 0)
assert.Len(t, m.Index.GetResolver().GetIgnoredCircularArrayReferences(), 1)
} }

View File

@@ -53,10 +53,23 @@ func NewResolver(index *SpecIndex) *Resolver {
if index == nil { if index == nil {
return nil return nil
} }
return &Resolver{ r := &Resolver{
specIndex: index, specIndex: index,
resolvedRoot: index.GetRootNode(), resolvedRoot: index.GetRootNode(),
} }
index.resolver = r
return r
}
// GetIgnoredCircularPolyReferences returns all ignored circular references that are polymorphic
func (resolver *Resolver) GetIgnoredCircularPolyReferences() []*CircularReferenceResult {
return resolver.ignoredPolyReferences
}
// GetIgnoredCircularArrayReferences returns all ignored circular references that are arrays
func (resolver *Resolver) GetIgnoredCircularArrayReferences() []*CircularReferenceResult {
return resolver.ignoredArrayReferences
} }
// GetResolvingErrors returns all errors found during resolving // GetResolvingErrors returns all errors found during resolving

View File

@@ -7,7 +7,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -318,9 +318,9 @@ func TestResolver_CheckForCircularReferences_DigitalOcean(t *testing.T) {
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification") baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")
idx := NewSpecIndexWithConfig(&rootNode, &SpecIndexConfig{ idx := NewSpecIndexWithConfig(&rootNode, &SpecIndexConfig{
AllowRemoteLookup: true, //AllowRemoteLookup: true,
AllowFileLookup: true, //AllowFileLookup: true,
BaseURL: baseURL, BaseURL: baseURL,
}) })
resolver := NewResolver(idx) resolver := NewResolver(idx)

View File

@@ -54,6 +54,9 @@ type Rolodex struct {
localFS map[string]fs.FS localFS map[string]fs.FS
remoteFS map[string]fs.FS remoteFS map[string]fs.FS
indexed bool indexed bool
built bool
resolved bool
circChecked bool
indexConfig *SpecIndexConfig indexConfig *SpecIndexConfig
indexingDuration time.Duration indexingDuration time.Duration
indexes []*SpecIndex indexes []*SpecIndex
@@ -324,11 +327,12 @@ func (r *Rolodex) IndexTheRolodex() error {
} }
// now that we have indexed all the files, we can build the index. // now that we have indexed all the files, we can build the index.
for _, idx := range indexBuildQueue {
idx.BuildIndex()
}
r.indexes = indexBuildQueue r.indexes = indexBuildQueue
if !r.indexConfig.AvoidBuildIndex {
for _, idx := range indexBuildQueue {
idx.BuildIndex()
}
}
// indexed and built every supporting file, we can build the root index (our entry point) // indexed and built every supporting file, we can build the root index (our entry point)
index := NewSpecIndexWithConfig(r.indexConfig.SpecInfo.RootNode, r.indexConfig) index := NewSpecIndexWithConfig(r.indexConfig.SpecInfo.RootNode, r.indexConfig)
@@ -339,10 +343,16 @@ func (r *Rolodex) IndexTheRolodex() error {
if r.indexConfig.IgnorePolymorphicCircularReferences { if r.indexConfig.IgnorePolymorphicCircularReferences {
resolver.IgnorePolymorphicCircularReferences() resolver.IgnorePolymorphicCircularReferences()
} }
index.resolver = resolver
resolvingErrors := resolver.CheckForCircularReferences() if !r.indexConfig.AvoidBuildIndex {
for e := range resolvingErrors { index.BuildIndex()
caughtErrors = append(caughtErrors, resolvingErrors[e]) }
if !r.indexConfig.AvoidCircularReferenceCheck {
resolvingErrors := resolver.CheckForCircularReferences()
for e := range resolvingErrors {
caughtErrors = append(caughtErrors, resolvingErrors[e])
}
} }
r.rootIndex = index r.rootIndex = index
@@ -353,6 +363,29 @@ func (r *Rolodex) IndexTheRolodex() error {
} }
func (r *Rolodex) CheckForCircularReferences() {
if r.rootIndex != nil && r.rootIndex.resolver != nil {
resolvingErrors := r.rootIndex.resolver.CheckForCircularReferences()
for e := range resolvingErrors {
r.caughtErrors = append(r.caughtErrors, resolvingErrors[e])
}
}
}
func (r *Rolodex) BuildIndexes() {
if r.built {
return
}
for _, idx := range r.indexes {
idx.BuildIndex()
}
if r.rootIndex != nil {
r.rootIndex.BuildIndex()
}
r.built = true
return
}
func (r *Rolodex) Open(location string) (RolodexFile, error) { func (r *Rolodex) Open(location string) (RolodexFile, error) {
var errorStack []error var errorStack []error
@@ -360,6 +393,10 @@ func (r *Rolodex) Open(location string) (RolodexFile, error) {
var localFile *LocalFile var localFile *LocalFile
//var remoteFile *RemoteFile //var remoteFile *RemoteFile
if r == nil || r.localFS == nil && r.remoteFS == nil {
panic("WHAT NO....")
}
for k, v := range r.localFS { for k, v := range r.localFS {
// check if this is a URL or an abs/rel reference. // check if this is a URL or an abs/rel reference.

View File

@@ -124,10 +124,13 @@ func NewLocalFS(baseDir string, dirFS fs.FS) (*LocalFS, error) {
if absBaseErr != nil { if absBaseErr != nil {
return nil, absBaseErr return nil, absBaseErr
} }
walkErr := fs.WalkDir(dirFS, baseDir, func(p string, d fs.DirEntry, err error) error { walkErr := fs.WalkDir(dirFS, ".", func(p string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// we don't care about directories. // we don't care about directories, or errors, just read everything we can.
if d.IsDir() { if d == nil || d.IsDir() {
return nil return nil
} }