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())
|
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
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pb33f/libopenapi/datamodel"
|
|
||||||
"golang.org/x/sync/syncmap"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pb33f/libopenapi/datamodel"
|
||||||
|
"golang.org/x/sync/syncmap"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -235,7 +236,7 @@ type SpecIndex struct {
|
|||||||
allRefSchemaDefinitions []*Reference // all schemas found that are references.
|
allRefSchemaDefinitions []*Reference // all schemas found that are references.
|
||||||
allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger).
|
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).
|
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
|
securitySchemesNode *yaml.Node // components/securitySchemes node
|
||||||
allSecuritySchemes map[string]*Reference // all security schemes / definitions.
|
allSecuritySchemes map[string]*Reference // all security schemes / definitions.
|
||||||
requestBodiesNode *yaml.Node // components/requestBodies node
|
requestBodiesNode *yaml.Node // components/requestBodies node
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isHttpMethod(val string) bool {
|
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.linksRefs = make(map[string]map[string][]*Reference)
|
||||||
index.callbackRefs = make(map[string]*Reference)
|
index.callbackRefs = make(map[string]*Reference)
|
||||||
index.externalSpecIndex = make(map[string]*SpecIndex)
|
index.externalSpecIndex = make(map[string]*SpecIndex)
|
||||||
index.allComponentSchemaDefinitions = make(map[string]*Reference)
|
index.allComponentSchemaDefinitions = &sync.Map{}
|
||||||
index.allParameters = make(map[string]*Reference)
|
index.allParameters = make(map[string]*Reference)
|
||||||
index.allSecuritySchemes = make(map[string]*Reference)
|
index.allSecuritySchemes = make(map[string]*Reference)
|
||||||
index.allRequestBodies = make(map[string]*Reference)
|
index.allRequestBodies = make(map[string]*Reference)
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import (
|
|||||||
|
|
||||||
type ContextKey string
|
type ContextKey string
|
||||||
|
|
||||||
const CurrentPathKey ContextKey = "currentPath"
|
const (
|
||||||
const FoundIndexKey ContextKey = "foundIndex"
|
CurrentPathKey ContextKey = "currentPath"
|
||||||
|
FoundIndexKey ContextKey = "foundIndex"
|
||||||
|
)
|
||||||
|
|
||||||
func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *Reference) (*Reference, *SpecIndex) {
|
func (index *SpecIndex) SearchIndexForReferenceByReference(fullRef *Reference) (*Reference, *SpecIndex) {
|
||||||
r, idx, _ := index.SearchIndexForReferenceByReferenceWithContext(context.Background(), fullRef)
|
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) {
|
func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx context.Context, searchRef *Reference) (*Reference, *SpecIndex, context.Context) {
|
||||||
|
|
||||||
if index.cache != nil {
|
if index.cache != nil {
|
||||||
if v, ok := index.cache.Load(searchRef.FullDefinition); ok {
|
if v, ok := index.cache.Load(searchRef.FullDefinition); ok {
|
||||||
return v.(*Reference), v.(*Reference).Index, context.WithValue(ctx, CurrentPathKey, v.(*Reference).RemoteLocation)
|
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])
|
refAlt = fmt.Sprintf("%s#/%s", absPath, uri[1])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if filepath.IsAbs(uri[0]) {
|
if filepath.IsAbs(uri[0]) {
|
||||||
roloLookup = uri[0]
|
roloLookup = uri[0]
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if strings.HasPrefix(uri[0], "http") {
|
if strings.HasPrefix(uri[0], "http") {
|
||||||
roloLookup = ref
|
roloLookup = ref
|
||||||
} else {
|
} else {
|
||||||
@@ -107,9 +106,11 @@ func (index *SpecIndex) SearchIndexForReferenceByReferenceWithContext(ctx contex
|
|||||||
return r, r.Index, context.WithValue(ctx, CurrentPathKey, r.RemoteLocation)
|
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)
|
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.
|
// 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)
|
index.logger.Error("unable to locate reference anywhere in the rolodex", "reference", ref)
|
||||||
}
|
}
|
||||||
return nil, index, ctx
|
return nil, index, ctx
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,16 @@ package index
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"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"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"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
|
// 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.
|
// GetAllComponentSchemas will return all schemas defined in the components section of the document.
|
||||||
func (index *SpecIndex) GetAllComponentSchemas() map[string]*Reference {
|
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.
|
// 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,
|
ParentNode: schemasNode,
|
||||||
RequiredRefProperties: extractDefinitionRequiredRefProperties(schemasNode, map[string][]string{}, fullDef, index),
|
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) {
|
func (index *SpecIndex) extractComponentSecuritySchemes(securitySchemesNode *yaml.Node, pathPrefix string) {
|
||||||
|
|
||||||
var name string
|
var name string
|
||||||
for i, schema := range securitySchemesNode.Content {
|
for i, schema := range securitySchemesNode.Content {
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
@@ -398,8 +397,7 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if paramRef != nil {
|
if paramRef != nil {
|
||||||
index.paramOpRefs[pathItemNode.Value][method][paramRefName] =
|
index.paramOpRefs[pathItemNode.Value][method][paramRefName] = append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef)
|
||||||
append(index.paramOpRefs[pathItemNode.Value][method][paramRefName], paramRef)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,13 +464,11 @@ func (index *SpecIndex) scanOperationParams(params []*yaml.Node, pathItemNode *y
|
|||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
index.paramOpRefs[pathItemNode.Value][method][ref.Name] =
|
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
|
||||||
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
index.paramOpRefs[pathItemNode.Value][method][ref.Name] =
|
index.paramOpRefs[pathItemNode.Value][method][ref.Name] = append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
|
||||||
append(index.paramOpRefs[pathItemNode.Value][method][ref.Name], ref)
|
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -489,7 +485,6 @@ func runIndexFunction(funcs []func() int, wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bool) string {
|
func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bool) string {
|
||||||
|
|
||||||
cleanedPath := baseURL.Path // not cleaned yet!
|
cleanedPath := baseURL.Path // not cleaned yet!
|
||||||
|
|
||||||
// create a slice of path segments from existing path
|
// create a slice of path segments from existing path
|
||||||
@@ -533,7 +528,17 @@ func GenerateCleanSpecConfigBaseURL(baseURL *url.URL, dir string, includeFile bo
|
|||||||
} else {
|
} else {
|
||||||
p = cleanedPath
|
p = cleanedPath
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return strings.TrimSuffix(p, "/")
|
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