Huge performance increase with building.

Using some designs unearthed from building the higher level model, I have brough that design down to the lower level to speed things up. It only took 8 years, but finally, I think I have mastered mult-threading. No more deadlocks, and no more need for waitgroups for everything.
This commit is contained in:
Dave Shanley
2022-08-22 09:46:44 -04:00
parent 6c2de6c151
commit 1a71f449ff
13 changed files with 562 additions and 332 deletions

View File

@@ -24,6 +24,10 @@ const (
var seenSchemas map[string]*Schema var seenSchemas map[string]*Schema
func init() { func init() {
clearSchemas()
}
func clearSchemas() {
seenSchemas = make(map[string]*Schema) seenSchemas = make(map[string]*Schema)
} }
@@ -108,7 +112,7 @@ func NewComponents(comp *low.Components) *Components {
} }
for k, v := range comp.Schemas.Value { for k, v := range comp.Schemas.Value {
go buildSchema(k, v.Value, schemaChan) go buildSchema(k, v, schemaChan)
} }
totalComponents := len(comp.Callbacks.Value) + len(comp.Links.Value) + len(comp.Responses.Value) + totalComponents := len(comp.Callbacks.Value) + len(comp.Links.Value) + len(comp.Responses.Value) +
@@ -169,12 +173,13 @@ func buildComponent[N any, O any](comp int, key string, orig O, c chan component
c <- componentResult[N]{comp: comp, res: f(orig), key: key} c <- componentResult[N]{comp: comp, res: f(orig), key: key}
} }
func buildSchema(key lowmodel.KeyReference[string], orig *low.Schema, c chan componentResult[*Schema]) { func buildSchema(key lowmodel.KeyReference[string], orig lowmodel.ValueReference[*low.Schema], c chan componentResult[*Schema]) {
var sch *Schema var sch *Schema
if ss := getSeenSchema(key.GenerateMapKey()); ss != nil { if ss := getSeenSchema(orig.GenerateMapKey()); ss != nil {
sch = ss sch = ss
} else { } else {
sch = NewSchema(orig) sch = NewSchema(orig.Value)
addSeenSchema(orig.GenerateMapKey(), sch)
} }
c <- componentResult[*Schema]{res: sch, key: key.Value} c <- componentResult[*Schema]{res: sch, key: key.Value}
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sync"
) )
const ( const (
@@ -16,6 +17,31 @@ const (
SchemasLabel = "schemas" SchemasLabel = "schemas"
) )
var seenSchemas map[string]*Schema
func init() {
clearSchemas()
}
func clearSchemas() {
seenSchemas = make(map[string]*Schema)
}
var seenSchemaLock sync.RWMutex
func addSeenSchema(key string, schema *Schema) {
defer seenSchemaLock.Unlock()
seenSchemaLock.Lock()
if seenSchemas[key] == nil {
seenSchemas[key] = schema
}
}
func getSeenSchema(key string) *Schema {
defer seenSchemaLock.Unlock()
seenSchemaLock.Lock()
return seenSchemas[key]
}
type Components struct { type Components struct {
Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]] Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]
Responses low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]] Responses low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]]
@@ -94,74 +120,56 @@ func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
n := 0 n := 0
total := 9 total := 9
var stateCheck = func() bool { for n < total {
n++
if n == total {
return true
}
return false
}
allDone:
for {
select { select {
case buildError := <-errorChan: case buildError := <-errorChan:
return buildError return buildError
case <-skipChan: case <-skipChan:
if stateCheck() { n++
break allDone
}
case params := <-paramChan: case params := <-paramChan:
co.Parameters = params co.Parameters = params
if stateCheck() { n++
break allDone
}
case schemas := <-schemaChan: case schemas := <-schemaChan:
co.Schemas = schemas co.Schemas = schemas
if stateCheck() { cacheSchemas(co.Schemas.Value)
break allDone n++
}
case responses := <-responsesChan: case responses := <-responsesChan:
co.Responses = responses co.Responses = responses
if stateCheck() { n++
break allDone
}
case examples := <-examplesChan: case examples := <-examplesChan:
co.Examples = examples co.Examples = examples
if stateCheck() { n++
break allDone
}
case reqBody := <-requestBodiesChan: case reqBody := <-requestBodiesChan:
co.RequestBodies = reqBody co.RequestBodies = reqBody
if stateCheck() { n++
break allDone
}
case headers := <-headersChan: case headers := <-headersChan:
co.Headers = headers co.Headers = headers
if stateCheck() { n++
break allDone
}
case sScheme := <-securitySchemesChan: case sScheme := <-securitySchemesChan:
co.SecuritySchemes = sScheme co.SecuritySchemes = sScheme
if stateCheck() { n++
break allDone
}
case links := <-linkChan: case links := <-linkChan:
co.Links = links co.Links = links
if stateCheck() { n++
break allDone
}
case callbacks := <-callbackChan: case callbacks := <-callbackChan:
co.Callbacks = callbacks co.Callbacks = callbacks
if stateCheck() { n++
break allDone
}
} }
} }
return nil return nil
} }
func cacheSchemas(sch map[low.KeyReference[string]]low.ValueReference[*Schema]) {
for _, v := range sch {
addSeenSchema(v.GenerateMapKey(), v.Value)
}
}
type componentBuildResult[T any] struct {
k low.KeyReference[string]
v low.ValueReference[T]
}
func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.Node, func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.Node,
skip chan bool, errorChan chan<- error, resultChan chan<- low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]], idx *index.SpecIndex) { skip chan bool, errorChan chan<- error, resultChan chan<- low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]], idx *index.SpecIndex) {
_, nodeLabel, nodeValue := utils.FindKeyNodeFull(label, root.Content) _, nodeLabel, nodeValue := utils.FindKeyNodeFull(label, root.Content)
@@ -175,25 +183,47 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml.
errorChan <- fmt.Errorf("node is array, cannot be used in components: line %d, column %d", nodeValue.Line, nodeValue.Column) errorChan <- fmt.Errorf("node is array, cannot be used in components: line %d, column %d", nodeValue.Line, nodeValue.Column)
return return
} }
// for every component, build in a new thread!
bChan := make(chan componentBuildResult[T])
var buildComponent = func(label *yaml.Node, value *yaml.Node, c chan componentBuildResult[T], ec chan<- error) {
var n T = new(N)
_ = low.BuildModel(value, n)
err := n.Build(value, idx)
if err != nil {
ec <- err
return
}
c <- componentBuildResult[T]{
k: low.KeyReference[string]{
KeyNode: label,
Value: label.Value,
},
v: low.ValueReference[T]{
Value: n,
ValueNode: value,
},
}
}
for i, v := range nodeValue.Content { for i, v := range nodeValue.Content {
if i%2 == 0 { if i%2 == 0 {
currentLabel = v currentLabel = v
continue continue
} }
var n T = new(N) go buildComponent(currentLabel, v, bChan, errorChan)
_ = low.BuildModel(v, n) }
err := n.Build(v, idx)
if err != nil { totalComponents := len(nodeValue.Content) / 2
errorChan <- err completedComponents := 0
} for completedComponents < totalComponents {
componentValues[low.KeyReference[string]{ select {
KeyNode: currentLabel, case r := <-bChan:
Value: currentLabel.Value, componentValues[r.k] = r.v
}] = low.ValueReference[T]{ completedComponents++
Value: n,
ValueNode: v,
} }
} }
results := low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]]{ results := low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]]{
KeyNode: nodeLabel, KeyNode: nodeLabel,
ValueNode: nodeValue, ValueNode: nodeValue,

View File

@@ -4,18 +4,25 @@ import (
"github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/resolver"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"sync" "sync"
) )
func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) { func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
// clean state
clearSchemas()
doc := Document{Version: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}} doc := Document{Version: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}}
// build an index // build an index
idx := index.NewSpecIndex(info.RootNode) idx := index.NewSpecIndex(info.RootNode)
doc.Index = idx doc.Index = idx
// create resolver and check for circular references.
resolve := resolver.NewResolver(idx)
_ = resolve.CheckForCircularReferences()
var wg sync.WaitGroup var wg sync.WaitGroup
var errors []error var errors []error
@@ -32,24 +39,23 @@ func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
wg.Done() wg.Done()
} }
extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{ extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{
extractInfo, extractInfo,
extractServers, extractServers,
extractTags, extractTags,
extractPaths,
extractComponents, extractComponents,
extractSecurity, extractSecurity,
extractExternalDocs, extractExternalDocs,
extractPaths,
} }
wg.Add(len(extractionFuncs)) wg.Add(len(extractionFuncs))
for _, f := range extractionFuncs { for _, f := range extractionFuncs {
go runExtraction(info, &doc, idx, f, &errors, &wg) go runExtraction(info, &doc, idx, f, &errors, &wg)
} }
wg.Wait() wg.Wait()
return &doc, errors return &doc, errors
} }
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {

View File

@@ -9,7 +9,10 @@ import (
var doc *Document var doc *Document
func init() { func initTest() {
if doc != nil {
return
}
data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml") data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
var err []error var err []error
@@ -27,8 +30,8 @@ func BenchmarkCreateDocument(b *testing.B) {
} }
} }
func BenchmarkCreateDocument_Stripe(b *testing.B) { func BenchmarkCreateDocument_Circular(b *testing.B) {
data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml") data, _ := ioutil.ReadFile("../../../test_specs/circular-tests.yaml")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := CreateDocument(info) _, err := CreateDocument(info)
@@ -38,6 +41,32 @@ func BenchmarkCreateDocument_Stripe(b *testing.B) {
} }
} }
func BenchmarkCreateDocument_k8s(b *testing.B) {
data, _ := ioutil.ReadFile("../../../test_specs/k8s.json")
info, _ := datamodel.ExtractSpecInfo(data)
for i := 0; i < b.N; i++ {
_, err := CreateDocument(info)
if err != nil {
panic("this should not error")
}
}
}
func BenchmarkCreateDocument_Stripe(b *testing.B) {
for i := 0; i < b.N; i++ {
data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml")
info, _ := datamodel.ExtractSpecInfo(data)
_, err := CreateDocument(info)
if err != nil {
panic("this should not error")
}
}
}
func BenchmarkCreateDocument_Petstore(b *testing.B) { func BenchmarkCreateDocument_Petstore(b *testing.B) {
data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json") data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json")
info, _ := datamodel.ExtractSpecInfo(data) info, _ := datamodel.ExtractSpecInfo(data)
@@ -50,12 +79,14 @@ func BenchmarkCreateDocument_Petstore(b *testing.B) {
} }
func TestCreateDocument(t *testing.T) { func TestCreateDocument(t *testing.T) {
initTest()
assert.Equal(t, "3.0.1", doc.Version.Value) assert.Equal(t, "3.0.1", doc.Version.Value)
assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value) assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value)
assert.NotEmpty(t, doc.Info.Value.Title.Value) assert.NotEmpty(t, doc.Info.Value.Title.Value)
} }
func TestCreateDocument_Info(t *testing.T) { func TestCreateDocument_Info(t *testing.T) {
initTest()
assert.Equal(t, "https://pb33f.io", doc.Info.Value.TermsOfService.Value) assert.Equal(t, "https://pb33f.io", doc.Info.Value.TermsOfService.Value)
assert.Equal(t, "pb33f", doc.Info.Value.Contact.Value.Name.Value) assert.Equal(t, "pb33f", doc.Info.Value.Contact.Value.Name.Value)
assert.Equal(t, "buckaroo@pb33f.io", doc.Info.Value.Contact.Value.Email.Value) assert.Equal(t, "buckaroo@pb33f.io", doc.Info.Value.Contact.Value.Email.Value)
@@ -65,6 +96,7 @@ func TestCreateDocument_Info(t *testing.T) {
} }
func TestCreateDocument_Servers(t *testing.T) { func TestCreateDocument_Servers(t *testing.T) {
initTest()
assert.Len(t, doc.Servers.Value, 2) assert.Len(t, doc.Servers.Value, 2)
server1 := doc.Servers.Value[0].Value server1 := doc.Servers.Value[0].Value
server2 := doc.Servers.Value[1].Value server2 := doc.Servers.Value[1].Value
@@ -89,6 +121,7 @@ func TestCreateDocument_Servers(t *testing.T) {
} }
func TestCreateDocument_Tags(t *testing.T) { func TestCreateDocument_Tags(t *testing.T) {
initTest()
assert.Len(t, doc.Tags.Value, 2) assert.Len(t, doc.Tags.Value, 2)
// tag1 // tag1
@@ -134,6 +167,7 @@ func TestCreateDocument_Tags(t *testing.T) {
} }
func TestCreateDocument_Paths(t *testing.T) { func TestCreateDocument_Paths(t *testing.T) {
initTest()
assert.Len(t, doc.Paths.Value.PathItems, 5) assert.Len(t, doc.Paths.Value.PathItems, 5)
burgerId := doc.Paths.Value.FindPath("/burgers/{burgerId}") burgerId := doc.Paths.Value.FindPath("/burgers/{burgerId}")
assert.NotNil(t, burgerId) assert.NotNil(t, burgerId)
@@ -262,6 +296,7 @@ func TestCreateDocument_Paths(t *testing.T) {
} }
func TestCreateDocument_Components_Schemas(t *testing.T) { func TestCreateDocument_Components_Schemas(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
assert.NotNil(t, components) assert.NotNil(t, components)
@@ -286,6 +321,7 @@ func TestCreateDocument_Components_Schemas(t *testing.T) {
} }
func TestCreateDocument_Components_SecuritySchemes(t *testing.T) { func TestCreateDocument_Components_SecuritySchemes(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
securitySchemes := components.SecuritySchemes.Value securitySchemes := components.SecuritySchemes.Value
assert.Len(t, securitySchemes, 3) assert.Len(t, securitySchemes, 3)
@@ -314,6 +350,7 @@ func TestCreateDocument_Components_SecuritySchemes(t *testing.T) {
} }
func TestCreateDocument_Components_Responses(t *testing.T) { func TestCreateDocument_Components_Responses(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
responses := components.Responses.Value responses := components.Responses.Value
assert.Len(t, responses, 1) assert.Len(t, responses, 1)
@@ -326,6 +363,7 @@ func TestCreateDocument_Components_Responses(t *testing.T) {
} }
func TestCreateDocument_Components_Examples(t *testing.T) { func TestCreateDocument_Components_Examples(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
examples := components.Examples.Value examples := components.Examples.Value
assert.Len(t, examples, 1) assert.Len(t, examples, 1)
@@ -337,6 +375,7 @@ func TestCreateDocument_Components_Examples(t *testing.T) {
} }
func TestCreateDocument_Components_RequestBodies(t *testing.T) { func TestCreateDocument_Components_RequestBodies(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
requestBodies := components.RequestBodies.Value requestBodies := components.RequestBodies.Value
assert.Len(t, requestBodies, 1) assert.Len(t, requestBodies, 1)
@@ -348,6 +387,7 @@ func TestCreateDocument_Components_RequestBodies(t *testing.T) {
} }
func TestCreateDocument_Components_Headers(t *testing.T) { func TestCreateDocument_Components_Headers(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
headers := components.Headers.Value headers := components.Headers.Value
assert.Len(t, headers, 1) assert.Len(t, headers, 1)
@@ -359,6 +399,7 @@ func TestCreateDocument_Components_Headers(t *testing.T) {
} }
func TestCreateDocument_Components_Links(t *testing.T) { func TestCreateDocument_Components_Links(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
links := components.Links.Value links := components.Links.Value
assert.Len(t, links, 2) assert.Len(t, links, 2)
@@ -373,6 +414,7 @@ func TestCreateDocument_Components_Links(t *testing.T) {
} }
func TestCreateDocument_Doc_Security(t *testing.T) { func TestCreateDocument_Doc_Security(t *testing.T) {
initTest()
security := doc.Security.Value security := doc.Security.Value
assert.NotNil(t, security) assert.NotNil(t, security)
assert.Len(t, security.ValueRequirements, 1) assert.Len(t, security.ValueRequirements, 1)
@@ -382,6 +424,7 @@ func TestCreateDocument_Doc_Security(t *testing.T) {
} }
func TestCreateDocument_Callbacks(t *testing.T) { func TestCreateDocument_Callbacks(t *testing.T) {
initTest()
callbacks := doc.Components.Value.Callbacks.Value callbacks := doc.Components.Value.Callbacks.Value
assert.Len(t, callbacks, 1) assert.Len(t, callbacks, 1)
@@ -396,6 +439,7 @@ func TestCreateDocument_Callbacks(t *testing.T) {
} }
func TestCreateDocument_Component_Discriminator(t *testing.T) { func TestCreateDocument_Component_Discriminator(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
dsc := components.FindSchema("Drink").Value.Discriminator.Value dsc := components.FindSchema("Drink").Value.Discriminator.Value
@@ -406,6 +450,7 @@ func TestCreateDocument_Component_Discriminator(t *testing.T) {
} }
func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) { func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
d := components.FindSchema("Dressing") d := components.FindSchema("Dressing")
assert.NotNil(t, d.Value.AdditionalProperties.Value) assert.NotNil(t, d.Value.AdditionalProperties.Value)
@@ -417,6 +462,7 @@ func TestCreateDocument_CheckAdditionalProperties_Schema(t *testing.T) {
} }
func TestCreateDocument_CheckAdditionalProperties_Bool(t *testing.T) { func TestCreateDocument_CheckAdditionalProperties_Bool(t *testing.T) {
initTest()
components := doc.Components.Value components := doc.Components.Value
d := components.FindSchema("Drink") d := components.FindSchema("Drink")
assert.NotNil(t, d.Value.AdditionalProperties.Value) assert.NotNil(t, d.Value.AdditionalProperties.Value)

View File

@@ -4,78 +4,78 @@
package v3 package v3
import ( import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type MediaType struct { type MediaType struct {
Schema low.NodeReference[*Schema] Schema low.NodeReference[*Schema]
Example low.NodeReference[any] Example low.NodeReference[any]
Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]] Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]
Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]] Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
func (mt *MediaType) FindExtension(ext string) *low.ValueReference[any] { func (mt *MediaType) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, mt.Extensions) return low.FindItemInMap[any](ext, mt.Extensions)
} }
func (mt *MediaType) FindPropertyEncoding(eType string) *low.ValueReference[*Encoding] { func (mt *MediaType) FindPropertyEncoding(eType string) *low.ValueReference[*Encoding] {
return low.FindItemInMap[*Encoding](eType, mt.Encoding.Value) return low.FindItemInMap[*Encoding](eType, mt.Encoding.Value)
} }
func (mt *MediaType) FindExample(eType string) *low.ValueReference[*Example] { func (mt *MediaType) FindExample(eType string) *low.ValueReference[*Example] {
return low.FindItemInMap[*Example](eType, mt.Examples.Value) return low.FindItemInMap[*Example](eType, mt.Examples.Value)
} }
func (mt *MediaType) GetAllExamples() map[low.KeyReference[string]]low.ValueReference[*Example] { func (mt *MediaType) GetAllExamples() map[low.KeyReference[string]]low.ValueReference[*Example] {
return mt.Examples.Value return mt.Examples.Value
} }
func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error { func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error {
mt.Extensions = low.ExtractExtensions(root) mt.Extensions = low.ExtractExtensions(root)
// handle example if set. // handle example if set.
_, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content) _, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content)
if expNode != nil { if expNode != nil {
mt.Example = low.NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode} mt.Example = low.NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode}
} }
// handle schema //handle schema
sch, sErr := ExtractSchema(root, idx) sch, sErr := ExtractSchema(root, idx)
if sErr != nil { if sErr != nil {
return sErr return sErr
} }
if sch != nil { if sch != nil {
mt.Schema = *sch mt.Schema = *sch
} }
// handle examples if set. // handle examples if set.
exps, expsL, expsN, eErr := low.ExtractMapFlat[*Example](ExamplesLabel, root, idx) exps, expsL, expsN, eErr := low.ExtractMapFlat[*Example](ExamplesLabel, root, idx)
if eErr != nil { if eErr != nil {
return eErr return eErr
} }
if exps != nil { if exps != nil {
mt.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]{ mt.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]{
Value: exps, Value: exps,
KeyNode: expsL, KeyNode: expsL,
ValueNode: expsN, ValueNode: expsN,
} }
} }
// handle encoding // handle encoding
encs, encsL, encsN, encErr := low.ExtractMapFlat[*Encoding](EncodingLabel, root, idx) encs, encsL, encsN, encErr := low.ExtractMapFlat[*Encoding](EncodingLabel, root, idx)
if encErr != nil { if encErr != nil {
return encErr return encErr
} }
if encs != nil { if encs != nil {
mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{ mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{
Value: encs, Value: encs,
KeyNode: encsL, KeyNode: encsL,
ValueNode: encsN, ValueNode: encsN,
} }
} }
return nil return nil
} }

View File

@@ -13,84 +13,110 @@ import (
) )
const ( const (
PathsLabel = "paths" PathsLabel = "paths"
GetLabel = "get" GetLabel = "get"
PostLabel = "post" PostLabel = "post"
PatchLabel = "patch" PatchLabel = "patch"
PutLabel = "put" PutLabel = "put"
DeleteLabel = "delete" DeleteLabel = "delete"
OptionsLabel = "options" OptionsLabel = "options"
HeadLabel = "head" HeadLabel = "head"
TraceLabel = "trace" TraceLabel = "trace"
) )
type Paths struct { type Paths struct {
PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem] PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
} }
func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] { func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] {
for k, p := range p.PathItems { for k, p := range p.PathItems {
if k.Value == path { if k.Value == path {
return &p return &p
} }
} }
return nil return nil
} }
func (p *Paths) FindExtension(ext string) *low.ValueReference[any] { func (p *Paths) FindExtension(ext string) *low.ValueReference[any] {
return low.FindItemInMap[any](ext, p.Extensions) return low.FindItemInMap[any](ext, p.Extensions)
} }
func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error { func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
skip := false skip := false
var currentNode *yaml.Node var currentNode *yaml.Node
pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem]) pathsMap := make(map[low.KeyReference[string]]low.ValueReference[*PathItem])
for i, pathNode := range root.Content { // build each new path, in a new thread.
if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") { type pathBuildResult struct {
skip = true k low.KeyReference[string]
continue v low.ValueReference[*PathItem]
} }
if skip {
skip = false
continue
}
if i%2 == 0 {
currentNode = pathNode
continue
}
if ok, _, _ := utils.IsNodeRefValue(pathNode); ok { bChan := make(chan pathBuildResult)
r := low.LocateRefNode(pathNode, idx) eChan := make(chan error)
if r != nil { var buildPathItem = func(cNode, pNode *yaml.Node, b chan<- pathBuildResult, e chan<- error) {
pathNode = r if ok, _, _ := utils.IsNodeRefValue(pNode); ok {
} else { r := low.LocateRefNode(pNode, idx)
return fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d", if r != nil {
pathNode.Content[1].Value, pathNode.Content[1].Line, pathNode.Content[1].Column) pNode = r
} } else {
} e <- fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d",
pNode.Content[1].Value, pNode.Content[1].Line, pNode.Content[1].Column)
return
}
}
var path = PathItem{} path := new(PathItem)
_ = low.BuildModel(pathNode, &path) _ = low.BuildModel(pNode, path)
err := path.Build(pathNode, idx) err := path.Build(pNode, idx)
if err != nil { if err != nil {
return err e <- err
} return
}
b <- pathBuildResult{
k: low.KeyReference[string]{
Value: cNode.Value,
KeyNode: cNode,
},
v: low.ValueReference[*PathItem]{
Value: path,
ValueNode: pNode,
},
}
}
// add bulk here pathCount := 0
pathsMap[low.KeyReference[string]{ for i, pathNode := range root.Content {
Value: currentNode.Value, if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") {
KeyNode: currentNode, skip = true
}] = low.ValueReference[*PathItem]{ continue
Value: &path, }
ValueNode: pathNode, if skip {
} skip = false
} continue
}
if i%2 == 0 {
currentNode = pathNode
continue
}
pathCount++
go buildPathItem(currentNode, pathNode, bChan, eChan)
}
p.PathItems = pathsMap completedItems := 0
return nil for completedItems < pathCount {
select {
case err := <-eChan:
return err
case res := <-bChan:
completedItems++
pathsMap[res.k] = res.v
}
}
p.PathItems = pathsMap
return nil
} }

View File

@@ -174,17 +174,13 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
n := 0 n := 0
allDone: total := len(ops)
for { for n < total {
select { select {
case buildError := <-opErrorChan: case buildError := <-opErrorChan:
return buildError return buildError
case <-opBuildChan: case <-opBuildChan:
n++ n++
if n == len(ops) {
break allDone
}
} }
} }

View File

@@ -75,7 +75,7 @@ func (r *Response) FindLink(hType string) *low.ValueReference[*Link] {
func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error { func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
// extract headers //extract headers
headers, lN, kN, err := low.ExtractMapFlat[*Header](HeadersLabel, root, idx) headers, lN, kN, err := low.ExtractMapFlat[*Header](HeadersLabel, root, idx)
if err != nil { if err != nil {
return err return err

View File

@@ -69,11 +69,15 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) error { func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) error {
level++
if low.IsCircular(root, idx) {
return nil // circular references cannot be built.
}
if level > 30 { if level > 30 {
return fmt.Errorf("schema is too nested to continue: %d levels deep, is too deep", level) // we're done, son! too fricken deep. return fmt.Errorf("schema is too nested to continue: %d levels deep, is too deep", level) // we're done, son! too fricken deep.
} }
level++
if h, _, _ := utils.IsNodeRefValue(root); h { if h, _, _ := utils.IsNodeRefValue(root); h {
ref := low.LocateRefNode(root, idx) ref := low.LocateRefNode(root, idx)
if ref != nil { if ref != nil {
@@ -135,11 +139,51 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode} s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode}
} }
// for property, build in a new thread!
bChan := make(chan schemaBuildResult)
eChan := make(chan error)
var buildProperty = func(label *yaml.Node, value *yaml.Node, c chan schemaBuildResult, ec chan<- error) {
// have we seen this before?
seen := getSeenSchema(fmt.Sprintf("%d:%d", value.Line, value.Column))
if seen != nil {
c <- schemaBuildResult{
k: low.KeyReference[string]{
KeyNode: label,
Value: label.Value,
},
v: low.ValueReference[*Schema]{
Value: seen,
ValueNode: value,
},
}
return
}
p := new(Schema)
_ = low.BuildModel(value, p)
err := p.BuildLevel(value, idx, level)
if err != nil {
ec <- err
return
}
c <- schemaBuildResult{
k: low.KeyReference[string]{
KeyNode: label,
Value: label.Value,
},
v: low.ValueReference[*Schema]{
Value: p,
ValueNode: value,
},
}
}
// handle properties // handle properties
_, propLabel, propsNode := utils.FindKeyNodeFull(PropertiesLabel, root.Content) _, propLabel, propsNode := utils.FindKeyNodeFull(PropertiesLabel, root.Content)
if propsNode != nil { if propsNode != nil {
propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*Schema]) propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*Schema])
var currentProp *yaml.Node var currentProp *yaml.Node
totalProps := 0
for i, prop := range propsNode.Content { for i, prop := range propsNode.Content {
if i%2 == 0 { if i%2 == 0 {
currentProp = prop currentProp = prop
@@ -156,19 +200,17 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
prop.Content[1].Value, prop.Content[1].Column, prop.Content[1].Line) prop.Content[1].Value, prop.Content[1].Column, prop.Content[1].Line)
} }
} }
totalProps++
var property Schema go buildProperty(currentProp, prop, bChan, eChan)
_ = low.BuildModel(prop, &property) }
err := property.BuildLevel(prop, idx, level) completedProps := 0
if err != nil { for completedProps < totalProps {
select {
case err := <-eChan:
return err return err
} case res := <-bChan:
propertyMap[low.KeyReference[string]{ completedProps++
Value: currentProp.Value, propertyMap[res.k] = res.v
KeyNode: currentProp,
}] = low.ValueReference[*Schema]{
Value: &property,
ValueNode: prop,
} }
} }
s.Properties = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]{ s.Properties = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]{
@@ -233,6 +275,11 @@ func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) er
return nil return nil
} }
type schemaBuildResult struct {
k low.KeyReference[string]
v low.ValueReference[*Schema]
}
func (s *Schema) extractExtensions(root *yaml.Node) { func (s *Schema) extractExtensions(root *yaml.Node) {
s.Extensions = low.ExtractExtensions(root) s.Extensions = low.ExtractExtensions(root)
} }
@@ -247,9 +294,9 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo
} }
if valueNode != nil { if valueNode != nil {
var build = func(kn *yaml.Node, vn *yaml.Node) *low.NodeReference[*Schema] {
var schema Schema
build := func(kn *yaml.Node, vn *yaml.Node) *low.NodeReference[*Schema] {
schema := new(Schema)
if h, _, _ := utils.IsNodeRefValue(vn); h { if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := low.LocateRefNode(vn, idx) ref := low.LocateRefNode(vn, idx)
if ref != nil { if ref != nil {
@@ -261,14 +308,28 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo
} }
} }
_ = low.BuildModel(vn, &schema) seen := getSeenSchema(fmt.Sprintf("%d:%d", vn.Line, vn.Column))
if seen != nil {
return &low.NodeReference[*Schema]{
Value: seen,
KeyNode: kn,
ValueNode: vn,
}
}
_ = low.BuildModel(vn, schema)
// add schema before we build, so it doesn't get stuck in an infinite loop.
addSeenSchema(fmt.Sprintf("%d:%d", vn.Line, vn.Column), schema)
err := schema.BuildLevel(vn, idx, level) err := schema.BuildLevel(vn, idx, level)
if err != nil { if err != nil {
*errors = append(*errors, err) *errors = append(*errors, err)
return nil return nil
} }
return &low.NodeReference[*Schema]{ return &low.NodeReference[*Schema]{
Value: &schema, Value: schema,
KeyNode: kn, KeyNode: kn,
ValueNode: vn, ValueNode: vn,
} }
@@ -292,8 +353,8 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo
} }
} }
if utils.IsNodeArray(valueNode) { if utils.IsNodeArray(valueNode) {
//fmt.Println("polymorphic looping sucks dude.")
for _, vn := range valueNode.Content { for _, vn := range valueNode.Content {
if h, _, _ := utils.IsNodeRefValue(vn); h { if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := low.LocateRefNode(vn, idx) ref := low.LocateRefNode(vn, idx)
if ref != nil { if ref != nil {
@@ -312,7 +373,6 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo
} }
} }
//wg.Done()
return labelNode, valueNode return labelNode, valueNode
} }
@@ -345,12 +405,20 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S
} }
if schNode != nil { if schNode != nil {
// check if schema has already been built.
seen := getSeenSchema(fmt.Sprintf("%d:%d", schNode.Line, schNode.Column))
if seen != nil {
return &low.NodeReference[*Schema]{Value: seen, KeyNode: schLabel, ValueNode: schNode}, nil
}
var schema Schema var schema Schema
_ = low.BuildModel(schNode, &schema) _ = low.BuildModel(schNode, &schema)
err := schema.Build(schNode, idx) err := schema.Build(schNode, idx)
addSeenSchema(fmt.Sprintf("%d:%d", schNode.Line, schNode.Column), &schema)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &low.NodeReference[*Schema]{Value: &schema, KeyNode: schLabel, ValueNode: schNode}, nil return &low.NodeReference[*Schema]{Value: &schema, KeyNode: schLabel, ValueNode: schNode}, nil
} }
return nil, nil return nil, nil

View File

@@ -9,7 +9,7 @@ import (
) )
func Test_Schema(t *testing.T) { func Test_Schema(t *testing.T) {
clearSchemas()
testSpec := `type: object testSpec := `type: object
description: something object description: something object
discriminator: discriminator:
@@ -221,7 +221,7 @@ additionalProperties: true `
} }
func TestSchema_BuildLevel_TooDeep(t *testing.T) { func TestSchema_BuildLevel_TooDeep(t *testing.T) {
clearSchemas()
// if you design data models like this, you're doing it fucking wrong. Seriously. why, what is so complex about a model // if you design data models like this, you're doing it fucking wrong. Seriously. why, what is so complex about a model
// that it needs to be 30+ levels deep? I have seen this shit in the wild, it's unreadable, un-parsable garbage. // that it needs to be 30+ levels deep? I have seen this shit in the wild, it's unreadable, un-parsable garbage.
yml := `type: object yml := `type: object
@@ -339,7 +339,7 @@ properties:
} }
func TestSchema_Build_ErrorAdditionalProps(t *testing.T) { func TestSchema_Build_ErrorAdditionalProps(t *testing.T) {
clearSchemas()
yml := `additionalProperties: yml := `additionalProperties:
$ref: #borko` $ref: #borko`
@@ -357,19 +357,19 @@ func TestSchema_Build_ErrorAdditionalProps(t *testing.T) {
} }
func TestSchema_Build_PropsLookup(t *testing.T) { func TestSchema_Build_PropsLookup(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
description: this is something description: this is something
type: string` type: string`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
properties: properties:
aValue: aValue:
$ref: '#/components/schemas/Something'` $ref: '#/components/schemas/Something'`
@@ -385,19 +385,19 @@ properties:
} }
func TestSchema_Build_PropsLookup_Fail(t *testing.T) { func TestSchema_Build_PropsLookup_Fail(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
description: this is something description: this is something
type: string` type: string`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
properties: properties:
aValue: aValue:
$ref: '#/bork'` $ref: '#/bork'`
@@ -412,8 +412,8 @@ properties:
} }
func Test_Schema_Polymorphism_Array_Ref(t *testing.T) { func Test_Schema_Polymorphism_Array_Ref(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
type: object type: object
@@ -425,11 +425,11 @@ func Test_Schema_Polymorphism_Array_Ref(t *testing.T) {
example: anything` example: anything`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
allOf: allOf:
- $ref: '#/components/schemas/Something' - $ref: '#/components/schemas/Something'
oneOf: oneOf:
@@ -460,8 +460,8 @@ items:
} }
func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) { func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
type: object type: object
@@ -473,11 +473,11 @@ func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) {
example: anything` example: anything`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
allOf: allOf:
- $ref: '#/components/schemas/Missing' - $ref: '#/components/schemas/Missing'
oneOf: oneOf:
@@ -502,8 +502,8 @@ items:
} }
func Test_Schema_Polymorphism_Map_Ref(t *testing.T) { func Test_Schema_Polymorphism_Map_Ref(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
type: object type: object
@@ -515,11 +515,11 @@ func Test_Schema_Polymorphism_Map_Ref(t *testing.T) {
example: anything` example: anything`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
allOf: allOf:
$ref: '#/components/schemas/Something' $ref: '#/components/schemas/Something'
oneOf: oneOf:
@@ -550,8 +550,8 @@ items:
} }
func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) { func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
type: object type: object
@@ -563,11 +563,11 @@ func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) {
example: anything` example: anything`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
allOf: allOf:
$ref: '#/components/schemas/Missing' $ref: '#/components/schemas/Missing'
oneOf: oneOf:
@@ -592,18 +592,19 @@ items:
} }
func Test_Schema_Polymorphism_BorkParent(t *testing.T) { func Test_Schema_Polymorphism_BorkParent(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
$ref: #borko` $ref: #borko`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
allOf: allOf:
$ref: '#/components/schemas/Something'` $ref: '#/components/schemas/Something'`
@@ -620,18 +621,19 @@ allOf:
} }
func Test_Schema_Polymorphism_BorkChild(t *testing.T) { func Test_Schema_Polymorphism_BorkChild(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
$ref: #borko` $ref: #borko`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
allOf: allOf:
$ref: #borko` $ref: #borko`
@@ -648,8 +650,9 @@ allOf:
} }
func Test_Schema_Polymorphism_RefMadness(t *testing.T) { func Test_Schema_Polymorphism_RefMadness(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
$ref: '#/components/schemas/Else' $ref: '#/components/schemas/Else'
@@ -657,11 +660,11 @@ func Test_Schema_Polymorphism_RefMadness(t *testing.T) {
description: madness` description: madness`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
allOf: allOf:
$ref: '#/components/schemas/Something'` $ref: '#/components/schemas/Something'`
@@ -681,8 +684,9 @@ allOf:
} }
func Test_Schema_Polymorphism_RefMadnessBork(t *testing.T) { func Test_Schema_Polymorphism_RefMadnessBork(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
$ref: '#/components/schemas/Else' $ref: '#/components/schemas/Else'
@@ -690,11 +694,11 @@ func Test_Schema_Polymorphism_RefMadnessBork(t *testing.T) {
$ref: #borko` $ref: #borko`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `type: object yml = `type: object
allOf: allOf:
$ref: '#/components/schemas/Something'` $ref: '#/components/schemas/Something'`
@@ -711,10 +715,11 @@ allOf:
} }
func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) { func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) {
clearSchemas()
// this does not work, but it won't error out. // this does not work, but it won't error out.
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
$ref: '#/components/schemas/Else' $ref: '#/components/schemas/Else'
@@ -722,11 +727,11 @@ func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) {
description: hey!` description: hey!`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `$ref: '#/components/schemas/Something'` yml = `$ref: '#/components/schemas/Something'`
var sch Schema var sch Schema
var idxNode yaml.Node var idxNode yaml.Node
@@ -741,19 +746,20 @@ func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) {
} }
func TestExtractSchema(t *testing.T) { func TestExtractSchema(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
description: this is something description: this is something
type: string` type: string`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `schema: yml = `schema:
type: object type: object
properties: properties:
aValue: aValue:
@@ -765,23 +771,25 @@ func TestExtractSchema(t *testing.T) {
res, err := ExtractSchema(idxNode.Content[0], idx) res, err := ExtractSchema(idxNode.Content[0], idx)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, res.Value) assert.NotNil(t, res.Value)
assert.Equal(t, "this is something", res.Value.FindProperty("aValue").Value.Description.Value) aValue := res.Value.FindProperty("aValue")
assert.Equal(t, "this is something", aValue.Value.Description.Value)
} }
func TestExtractSchema_Ref(t *testing.T) { func TestExtractSchema_Ref(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
description: this is something description: this is something
type: string` type: string`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `schema: yml = `schema:
$ref: '#/components/schemas/Something'` $ref: '#/components/schemas/Something'`
var idxNode yaml.Node var idxNode yaml.Node
@@ -794,19 +802,20 @@ func TestExtractSchema_Ref(t *testing.T) {
} }
func TestExtractSchema_Ref_Fail(t *testing.T) { func TestExtractSchema_Ref_Fail(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
description: this is something description: this is something
type: string` type: string`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `schema: yml = `schema:
$ref: '#/components/schemas/Missing'` $ref: '#/components/schemas/Missing'`
var idxNode yaml.Node var idxNode yaml.Node
@@ -817,19 +826,20 @@ func TestExtractSchema_Ref_Fail(t *testing.T) {
} }
func TestExtractSchema_RefRoot(t *testing.T) { func TestExtractSchema_RefRoot(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
description: this is something description: this is something
type: string` type: string`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `$ref: '#/components/schemas/Something'` yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)
@@ -841,19 +851,20 @@ func TestExtractSchema_RefRoot(t *testing.T) {
} }
func TestExtractSchema_RefRoot_Fail(t *testing.T) { func TestExtractSchema_RefRoot_Fail(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
description: this is something description: this is something
type: string` type: string`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `$ref: '#/components/schemas/Missing'` yml = `$ref: '#/components/schemas/Missing'`
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)
@@ -864,18 +875,19 @@ func TestExtractSchema_RefRoot_Fail(t *testing.T) {
} }
func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) { func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) {
clearSchemas()
doc := `components: yml := `components:
schemas: schemas:
Something: Something:
$ref: #bork` $ref: #bork`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `$ref: '#/components/schemas/Something'` yml = `$ref: '#/components/schemas/Something'`
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)
@@ -887,17 +899,19 @@ func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) {
func TestExtractSchema_DoNothing(t *testing.T) { func TestExtractSchema_DoNothing(t *testing.T) {
doc := `components: clearSchemas()
yml := `components:
schemas: schemas:
Something: Something:
$ref: #bork` $ref: #bork`
var iNode yaml.Node var iNode yaml.Node
mErr := yaml.Unmarshal([]byte(doc), &iNode) mErr := yaml.Unmarshal([]byte(yml), &iNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&iNode) idx := index.NewSpecIndex(&iNode)
yml := `please: do nothing.` yml = `please: do nothing.`
var idxNode yaml.Node var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode) _ = yaml.Unmarshal([]byte(yml), &idxNode)

View File

@@ -285,6 +285,11 @@ func ExtractMapFlatNoLookup[PT Buildable[N], N any](root *yaml.Node, idx *index.
return valueMap, nil return valueMap, nil
} }
type mappingResult[T any] struct {
k KeyReference[string]
v ValueReference[T]
}
func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) { func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) {
var labelNode, valueNode *yaml.Node var labelNode, valueNode *yaml.Node
if rf, rl, _ := utils.IsNodeRefValue(root); rf { if rf, rl, _ := utils.IsNodeRefValue(root); rf {
@@ -314,12 +319,36 @@ func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *
if valueNode != nil { if valueNode != nil {
var currentLabelNode *yaml.Node var currentLabelNode *yaml.Node
valueMap := make(map[KeyReference[string]]ValueReference[PT]) valueMap := make(map[KeyReference[string]]ValueReference[PT])
bChan := make(chan mappingResult[PT])
eChan := make(chan error)
var buildMap = func(label *yaml.Node, value *yaml.Node, c chan mappingResult[PT], ec chan<- error) {
var n PT = new(N)
_ = BuildModel(value, n)
err := n.Build(value, idx)
if err != nil {
ec <- err
return
}
c <- mappingResult[PT]{
k: KeyReference[string]{
KeyNode: label,
Value: label.Value,
},
v: ValueReference[PT]{
Value: n,
ValueNode: value,
},
}
}
totalKeys := 0
for i, en := range valueNode.Content { for i, en := range valueNode.Content {
if i%2 == 0 { if i%2 == 0 {
currentLabelNode = en currentLabelNode = en
continue continue
} }
// check our valueNode isn't a reference still. // check our valueNode isn't a reference still.
if h, _, _ := utils.IsNodeRefValue(en); h { if h, _, _ := utils.IsNodeRefValue(en); h {
ref := LocateRefNode(en, idx) ref := LocateRefNode(en, idx)
@@ -334,21 +363,18 @@ func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *
if strings.HasPrefix(strings.ToLower(currentLabelNode.Value), "x-") { if strings.HasPrefix(strings.ToLower(currentLabelNode.Value), "x-") {
continue // yo, don't pay any attention to extensions, not here anyway. continue // yo, don't pay any attention to extensions, not here anyway.
} }
var n PT = new(N) totalKeys++
err := BuildModel(en, n) go buildMap(currentLabelNode, en, bChan, eChan)
if err != nil { }
return nil, labelNode, valueNode, err
} completedKeys := 0
berr := n.Build(en, idx) for completedKeys < totalKeys {
if berr != nil { select {
return nil, labelNode, valueNode, berr case err := <-eChan:
} return valueMap, labelNode, valueNode, err
valueMap[KeyReference[string]{ case res := <-bChan:
Value: currentLabelNode.Value, completedKeys++
KeyNode: currentLabelNode, valueMap[res.k] = res.v
}] = ValueReference[PT]{
Value: n,
ValueNode: en,
} }
} }
return valueMap, labelNode, valueNode, nil return valueMap, labelNode, valueNode, nil

View File

@@ -54,3 +54,16 @@ func (n KeyReference[T]) IsEmpty() bool {
func (n KeyReference[T]) GenerateMapKey() string { func (n KeyReference[T]) GenerateMapKey() string {
return fmt.Sprintf("%d:%d", n.KeyNode.Line, n.KeyNode.Column) return fmt.Sprintf("%d:%d", n.KeyNode.Line, n.KeyNode.Column)
} }
func IsCircular(node *yaml.Node, idx *index.SpecIndex) bool {
if idx == nil {
return false // no index! nothing we can do.
}
refs := idx.GetCircularReferences()
for i := range idx.GetCircularReferences() {
if refs[i].LoopPoint.Node == node {
return true
}
}
return false
}

View File

@@ -1,89 +1,89 @@
package resolver package resolver
import ( import (
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io/ioutil" "io/ioutil"
"testing" "testing"
) )
func TestNewResolver(t *testing.T) { func TestNewResolver(t *testing.T) {
assert.Nil(t, NewResolver(nil)) assert.Nil(t, NewResolver(nil))
} }
func Benchmark_ResolveDocumentStripe(b *testing.B) { func Benchmark_ResolveDocumentStripe(b *testing.B) {
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(stripe, &rootNode) yaml.Unmarshal(stripe, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
resolver.Resolve() resolver.Resolve()
} }
} }
func TestResolver_ResolveComponents_CircularSpec(t *testing.T) { func TestResolver_ResolveComponents_CircularSpec(t *testing.T) {
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml") circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(circular, &rootNode) yaml.Unmarshal(circular, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 3) assert.Len(t, circ, 4)
_, err := yaml.Marshal(resolver.resolvedRoot) _, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestResolver_ResolveComponents_Stripe(t *testing.T) { func TestResolver_ResolveComponents_Stripe(t *testing.T) {
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(stripe, &rootNode) yaml.Unmarshal(stripe, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 0) assert.Len(t, circ, 229)
} }
func TestResolver_ResolveComponents_MixedRef(t *testing.T) { func TestResolver_ResolveComponents_MixedRef(t *testing.T) {
mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml") mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(mixedref, &rootNode) yaml.Unmarshal(mixedref, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 2) assert.Len(t, circ, 2)
} }
func TestResolver_ResolveComponents_k8s(t *testing.T) { func TestResolver_ResolveComponents_k8s(t *testing.T) {
k8s, _ := ioutil.ReadFile("../test_specs/k8s.json") k8s, _ := ioutil.ReadFile("../test_specs/k8s.json")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(k8s, &rootNode) yaml.Unmarshal(k8s, &rootNode)
index := index.NewSpecIndex(&rootNode) index := index.NewSpecIndex(&rootNode)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 0) assert.Len(t, circ, 1)
} }