mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-07 20:47:45 +00:00
fix: data race
This commit is contained in:
@@ -1191,3 +1191,61 @@ func TestDocument_AdvanceCallbackReferences(t *testing.T) {
|
||||
|
||||
assert.Empty(t, buf.String())
|
||||
}
|
||||
|
||||
func BenchmarkLoadDocTwice(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
spec, err := os.ReadFile("test_specs/speakeasy-test.yaml")
|
||||
require.NoError(b, err)
|
||||
|
||||
doc, err := NewDocumentWithConfiguration(spec, &datamodel.DocumentConfiguration{
|
||||
BasePath: "./test_specs",
|
||||
IgnorePolymorphicCircularReferences: true,
|
||||
IgnoreArrayCircularReferences: true,
|
||||
AllowFileReferences: true,
|
||||
})
|
||||
require.NoError(b, err)
|
||||
|
||||
_, errs := doc.BuildV3Model()
|
||||
require.Empty(b, errs)
|
||||
|
||||
doc, err = NewDocumentWithConfiguration(spec, &datamodel.DocumentConfiguration{
|
||||
BasePath: "./test_specs",
|
||||
IgnorePolymorphicCircularReferences: true,
|
||||
IgnoreArrayCircularReferences: true,
|
||||
AllowFileReferences: true,
|
||||
})
|
||||
require.NoError(b, err)
|
||||
|
||||
_, errs = doc.BuildV3Model()
|
||||
require.Empty(b, errs)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument_LoadDocTwice(t *testing.T) {
|
||||
spec, err := os.ReadFile("test_specs/speakeasy-test.yaml")
|
||||
require.NoError(t, err)
|
||||
|
||||
doc, err := NewDocumentWithConfiguration(spec, &datamodel.DocumentConfiguration{
|
||||
BasePath: "./test_specs",
|
||||
IgnorePolymorphicCircularReferences: true,
|
||||
IgnoreArrayCircularReferences: true,
|
||||
AllowFileReferences: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, errs := doc.BuildV3Model()
|
||||
require.Empty(t, errs)
|
||||
|
||||
doc, err = NewDocumentWithConfiguration(spec, &datamodel.DocumentConfiguration{
|
||||
BasePath: "./test_specs",
|
||||
IgnorePolymorphicCircularReferences: true,
|
||||
IgnoreArrayCircularReferences: true,
|
||||
AllowFileReferences: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, errs = doc.BuildV3Model()
|
||||
require.Empty(t, errs)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"github.com/pb33f/libopenapi/datamodel"
|
||||
"golang.org/x/sync/syncmap"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/pb33f/libopenapi/datamodel"
|
||||
"golang.org/x/sync/syncmap"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -235,7 +236,7 @@ type SpecIndex struct {
|
||||
allRefSchemaDefinitions []*Reference // all schemas found that are references.
|
||||
allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger).
|
||||
allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger).
|
||||
allComponentSchemaDefinitions map[string]*Reference // all schemas found in components (openapi) or definitions (swagger).
|
||||
allComponentSchemaDefinitions *syncmap.Map // all schemas found in components (openapi) or definitions (swagger).
|
||||
securitySchemesNode *yaml.Node // components/securitySchemes node
|
||||
allSecuritySchemes map[string]*Reference // all security schemes / definitions.
|
||||
requestBodiesNode *yaml.Node // components/requestBodies node
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v3"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func isHttpMethod(val string) bool {
|
||||
@@ -52,7 +54,7 @@ func boostrapIndexCollections(rootNode *yaml.Node, index *SpecIndex) {
|
||||
index.linksRefs = make(map[string]map[string][]*Reference)
|
||||
index.callbackRefs = make(map[string]*Reference)
|
||||
index.externalSpecIndex = make(map[string]*SpecIndex)
|
||||
index.allComponentSchemaDefinitions = make(map[string]*Reference)
|
||||
index.allComponentSchemaDefinitions = &sync.Map{}
|
||||
index.allParameters = make(map[string]*Reference)
|
||||
index.allSecuritySchemes = make(map[string]*Reference)
|
||||
index.allRequestBodies = make(map[string]*Reference)
|
||||
|
||||
@@ -13,8 +13,10 @@ import (
|
||||
|
||||
type ContextKey string
|
||||
|
||||
const CurrentPathKey ContextKey = "currentPath"
|
||||
const FoundIndexKey ContextKey = "foundIndex"
|
||||
const (
|
||||
CurrentPathKey ContextKey = "currentPath"
|
||||
FoundIndexKey ContextKey = "foundIndex"
|
||||
)
|
||||
|
||||
func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *Reference) (*Reference, *SpecIndex) {
|
||||
r, idx, _ := index.SearchIndexForReferenceByReferenceWithContext(context.Background(), fullRef)
|
||||
@@ -33,7 +35,6 @@ func (index *SpecIndex) SearchIndexForReferenceWithContext(ctx context.Context,
|
||||
}
|
||||
|
||||
func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx context.Context, searchRef *Reference) (*Reference, *SpecIndex, context.Context) {
|
||||
|
||||
if index.cache != nil {
|
||||
if v, ok := index.cache.Load(searchRef.FullDefinition); ok {
|
||||
return v.(*Reference), v.(*Reference).Index, context.WithValue(ctx, CurrentPathKey, v.(*Reference).RemoteLocation)
|
||||
@@ -74,12 +75,10 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex
|
||||
refAlt = fmt.Sprintf("%s#/%s", absPath, uri[1])
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
if filepath.IsAbs(uri[0]) {
|
||||
roloLookup = uri[0]
|
||||
} else {
|
||||
|
||||
if strings.HasPrefix(uri[0], "http") {
|
||||
roloLookup = ref
|
||||
} else {
|
||||
@@ -107,9 +106,11 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex
|
||||
return r, r.Index, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
|
||||
}
|
||||
|
||||
if r, ok := index.allComponentSchemaDefinitions[refAlt]; ok {
|
||||
if r, ok := index.allComponentSchemaDefinitions.Load(refAlt); ok {
|
||||
ref := r.(*Reference)
|
||||
|
||||
index.cache.Store(refAlt, r)
|
||||
return r, r.Index, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
|
||||
return ref, ref.Index, context.WithValue(ctx, CurrentPathKey, ref.RemoteLocation)
|
||||
}
|
||||
|
||||
// check the rolodex for the reference.
|
||||
@@ -184,5 +185,4 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex
|
||||
index.logger.Error("unable to locate reference anywhere in the rolodex", "reference", ref)
|
||||
}
|
||||
return nil, index, ctx
|
||||
|
||||
}
|
||||
|
||||
@@ -14,15 +14,16 @@ package index
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pb33f/libopenapi/utils"
|
||||
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
|
||||
"golang.org/x/sync/syncmap"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pb33f/libopenapi/utils"
|
||||
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
|
||||
"golang.org/x/sync/syncmap"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// NewSpecIndexWithConfig will create a new index of an OpenAPI or Swagger spec. It uses the same logic as NewSpecIndex
|
||||
@@ -284,7 +285,7 @@ func (index *SpecIndex) GetAllReferenceSchemas() []*Reference {
|
||||
|
||||
// GetAllComponentSchemas will return all schemas defined in the components section of the document.
|
||||
func (index *SpecIndex) GetAllComponentSchemas() map[string]*Reference {
|
||||
return index.allComponentSchemaDefinitions
|
||||
return syncMapToMap[string, *Reference](index.allComponentSchemaDefinitions)
|
||||
}
|
||||
|
||||
// GetAllSecuritySchemes will return all security schemes / definitions found in the document.
|
||||
|
||||
@@ -34,7 +34,7 @@ func (index *SpecIndex) extractDefinitionsAndSchemas(schemasNode *yaml.Node, pat
|
||||
ParentNode: schemasNode,
|
||||
RequiredRefProperties: extractDefinitionRequiredRefProperties(schemasNode, map[string][]string{}, fullDef, index),
|
||||
}
|
||||
index.allComponentSchemaDefinitions[def] = ref
|
||||
index.allComponentSchemaDefinitions.Store(def, ref)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,6 @@ func (index *SpecIndex) extractComponentExamples(examplesNode *yaml.Node, pathPr
|
||||
}
|
||||
|
||||
func (index *SpecIndex) extractComponentSecuritySchemes(securitySchemesNode *yaml.Node, pathPrefix string) {
|
||||
|
||||
var name string
|
||||
for i, schema := range securitySchemesNode.Content {
|
||||
if i%2 == 0 {
|
||||
@@ -398,8 +397,7 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y
|
||||
})
|
||||
} else {
|
||||
if paramRef != nil {
|
||||
index.paramOpRefs[pathItemNode.Value][method][paramRefName] =
|
||||
append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef)
|
||||
index.paramOpRefs[pathItemNode.Value][method][paramRefName] = append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,13 +464,11 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y
|
||||
Path: path,
|
||||
})
|
||||
} else {
|
||||
index.paramOpRefs[pathItemNode.Value][method][ref.Name] =
|
||||
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
|
||||
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
index.paramOpRefs[pathItemNode.Value][method][ref.Name] =
|
||||
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
|
||||
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -489,7 +485,6 @@ func runIndexFunction(funcs []func() int, wg *sync.WaitGroup) {
|
||||
}
|
||||
|
||||
func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bool) string {
|
||||
|
||||
cleanedPath := baseURL.Path // not cleaned yet!
|
||||
|
||||
// create a slice of path segments from existing path
|
||||
@@ -533,7 +528,17 @@ func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bo
|
||||
} else {
|
||||
p = cleanedPath
|
||||
}
|
||||
|
||||
}
|
||||
return strings.TrimSuffix(p, "/")
|
||||
}
|
||||
|
||||
func syncMapToMap[K comparable, V any](sm *sync.Map) map[K]V {
|
||||
m := make(map[K]V)
|
||||
|
||||
sm.Range(func(key, value interface{}) bool {
|
||||
m[key.(K)] = value.(V)
|
||||
return true
|
||||
})
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user