From 1a71f449ff7f759dd879c09d8302ea6945b10bc4 Mon Sep 17 00:00:00 2001 From: Dave Shanley Date: Mon, 22 Aug 2022 09:46:44 -0400 Subject: [PATCH] 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. --- datamodel/high/3.0/components.go | 13 +- datamodel/low/3.0/components.go | 136 ++++++++++++-------- datamodel/low/3.0/create_document.go | 14 +- datamodel/low/3.0/create_document_test.go | 52 +++++++- datamodel/low/3.0/media_type.go | 104 +++++++-------- datamodel/low/3.0/path.go | 148 +++++++++++++--------- datamodel/low/3.0/path_item.go | 8 +- datamodel/low/3.0/response.go | 2 +- datamodel/low/3.0/schema.go | 108 +++++++++++++--- datamodel/low/3.0/schema_test.go | 142 +++++++++++---------- datamodel/low/extraction_functions.go | 58 ++++++--- datamodel/low/reference.go | 13 ++ resolver/resolver_test.go | 96 +++++++------- 13 files changed, 562 insertions(+), 332 deletions(-) diff --git a/datamodel/high/3.0/components.go b/datamodel/high/3.0/components.go index 19a9dea..cd143ab 100644 --- a/datamodel/high/3.0/components.go +++ b/datamodel/high/3.0/components.go @@ -24,6 +24,10 @@ const ( var seenSchemas map[string]*Schema func init() { + clearSchemas() +} + +func clearSchemas() { seenSchemas = make(map[string]*Schema) } @@ -108,7 +112,7 @@ func NewComponents(comp *low.Components) *Components { } 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) + @@ -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} } -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 - if ss := getSeenSchema(key.GenerateMapKey()); ss != nil { + if ss := getSeenSchema(orig.GenerateMapKey()); ss != nil { sch = ss } else { - sch = NewSchema(orig) + sch = NewSchema(orig.Value) + addSeenSchema(orig.GenerateMapKey(), sch) } c <- componentResult[*Schema]{res: sch, key: key.Value} } diff --git a/datamodel/low/3.0/components.go b/datamodel/low/3.0/components.go index 9ddaaad..3571fd6 100644 --- a/datamodel/low/3.0/components.go +++ b/datamodel/low/3.0/components.go @@ -9,6 +9,7 @@ import ( "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" + "sync" ) const ( @@ -16,6 +17,31 @@ const ( 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 { Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]] 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 total := 9 - var stateCheck = func() bool { - n++ - if n == total { - return true - } - return false - } - -allDone: - for { + for n < total { select { case buildError := <-errorChan: return buildError case <-skipChan: - if stateCheck() { - break allDone - } + n++ case params := <-paramChan: co.Parameters = params - if stateCheck() { - break allDone - } + n++ case schemas := <-schemaChan: co.Schemas = schemas - if stateCheck() { - break allDone - } + cacheSchemas(co.Schemas.Value) + n++ case responses := <-responsesChan: co.Responses = responses - if stateCheck() { - break allDone - } + n++ case examples := <-examplesChan: co.Examples = examples - if stateCheck() { - break allDone - } + n++ case reqBody := <-requestBodiesChan: co.RequestBodies = reqBody - if stateCheck() { - break allDone - } + n++ case headers := <-headersChan: co.Headers = headers - if stateCheck() { - break allDone - } + n++ case sScheme := <-securitySchemesChan: co.SecuritySchemes = sScheme - if stateCheck() { - break allDone - } + n++ case links := <-linkChan: co.Links = links - if stateCheck() { - break allDone - } + n++ case callbacks := <-callbackChan: co.Callbacks = callbacks - if stateCheck() { - break allDone - } + n++ } } - 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, 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) @@ -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) 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 { if i%2 == 0 { currentLabel = v continue } - var n T = new(N) - _ = low.BuildModel(v, n) - err := n.Build(v, idx) - if err != nil { - errorChan <- err - } - componentValues[low.KeyReference[string]{ - KeyNode: currentLabel, - Value: currentLabel.Value, - }] = low.ValueReference[T]{ - Value: n, - ValueNode: v, + go buildComponent(currentLabel, v, bChan, errorChan) + } + + totalComponents := len(nodeValue.Content) / 2 + completedComponents := 0 + for completedComponents < totalComponents { + select { + case r := <-bChan: + componentValues[r.k] = r.v + completedComponents++ } } + results := low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]]{ KeyNode: nodeLabel, ValueNode: nodeValue, diff --git a/datamodel/low/3.0/create_document.go b/datamodel/low/3.0/create_document.go index eb44769..e5b11fa 100644 --- a/datamodel/low/3.0/create_document.go +++ b/datamodel/low/3.0/create_document.go @@ -4,18 +4,25 @@ import ( "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/resolver" "github.com/pb33f/libopenapi/utils" "sync" ) func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) { + // clean state + clearSchemas() doc := Document{Version: low.ValueReference[string]{Value: info.Version, ValueNode: info.RootNode}} // build an index idx := index.NewSpecIndex(info.RootNode) doc.Index = idx + // create resolver and check for circular references. + resolve := resolver.NewResolver(idx) + _ = resolve.CheckForCircularReferences() + var wg sync.WaitGroup var errors []error @@ -32,24 +39,23 @@ func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) { wg.Done() } - extractionFuncs := []func(i *datamodel.SpecInfo, d *Document, idx *index.SpecIndex) error{ extractInfo, extractServers, extractTags, - extractPaths, extractComponents, extractSecurity, extractExternalDocs, + extractPaths, } + wg.Add(len(extractionFuncs)) for _, f := range extractionFuncs { go runExtraction(info, &doc, idx, f, &errors, &wg) } + wg.Wait() - return &doc, errors - } func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error { diff --git a/datamodel/low/3.0/create_document_test.go b/datamodel/low/3.0/create_document_test.go index 061ffc5..6289572 100644 --- a/datamodel/low/3.0/create_document_test.go +++ b/datamodel/low/3.0/create_document_test.go @@ -9,7 +9,10 @@ import ( var doc *Document -func init() { +func initTest() { + if doc != nil { + return + } data, _ := ioutil.ReadFile("../../../test_specs/burgershop.openapi.yaml") info, _ := datamodel.ExtractSpecInfo(data) var err []error @@ -27,8 +30,8 @@ func BenchmarkCreateDocument(b *testing.B) { } } -func BenchmarkCreateDocument_Stripe(b *testing.B) { - data, _ := ioutil.ReadFile("../../../test_specs/stripe.yaml") +func BenchmarkCreateDocument_Circular(b *testing.B) { + data, _ := ioutil.ReadFile("../../../test_specs/circular-tests.yaml") info, _ := datamodel.ExtractSpecInfo(data) for i := 0; i < b.N; i++ { _, 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) { data, _ := ioutil.ReadFile("../../../test_specs/petstorev3.json") info, _ := datamodel.ExtractSpecInfo(data) @@ -50,12 +79,14 @@ func BenchmarkCreateDocument_Petstore(b *testing.B) { } func TestCreateDocument(t *testing.T) { + initTest() assert.Equal(t, "3.0.1", doc.Version.Value) assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value) assert.NotEmpty(t, doc.Info.Value.Title.Value) } func TestCreateDocument_Info(t *testing.T) { + initTest() 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, "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) { + initTest() assert.Len(t, doc.Servers.Value, 2) server1 := doc.Servers.Value[0].Value server2 := doc.Servers.Value[1].Value @@ -89,6 +121,7 @@ func TestCreateDocument_Servers(t *testing.T) { } func TestCreateDocument_Tags(t *testing.T) { + initTest() assert.Len(t, doc.Tags.Value, 2) // tag1 @@ -134,6 +167,7 @@ func TestCreateDocument_Tags(t *testing.T) { } func TestCreateDocument_Paths(t *testing.T) { + initTest() assert.Len(t, doc.Paths.Value.PathItems, 5) burgerId := doc.Paths.Value.FindPath("/burgers/{burgerId}") assert.NotNil(t, burgerId) @@ -262,6 +296,7 @@ func TestCreateDocument_Paths(t *testing.T) { } func TestCreateDocument_Components_Schemas(t *testing.T) { + initTest() components := doc.Components.Value assert.NotNil(t, components) @@ -286,6 +321,7 @@ func TestCreateDocument_Components_Schemas(t *testing.T) { } func TestCreateDocument_Components_SecuritySchemes(t *testing.T) { + initTest() components := doc.Components.Value securitySchemes := components.SecuritySchemes.Value assert.Len(t, securitySchemes, 3) @@ -314,6 +350,7 @@ func TestCreateDocument_Components_SecuritySchemes(t *testing.T) { } func TestCreateDocument_Components_Responses(t *testing.T) { + initTest() components := doc.Components.Value responses := components.Responses.Value assert.Len(t, responses, 1) @@ -326,6 +363,7 @@ func TestCreateDocument_Components_Responses(t *testing.T) { } func TestCreateDocument_Components_Examples(t *testing.T) { + initTest() components := doc.Components.Value examples := components.Examples.Value assert.Len(t, examples, 1) @@ -337,6 +375,7 @@ func TestCreateDocument_Components_Examples(t *testing.T) { } func TestCreateDocument_Components_RequestBodies(t *testing.T) { + initTest() components := doc.Components.Value requestBodies := components.RequestBodies.Value assert.Len(t, requestBodies, 1) @@ -348,6 +387,7 @@ func TestCreateDocument_Components_RequestBodies(t *testing.T) { } func TestCreateDocument_Components_Headers(t *testing.T) { + initTest() components := doc.Components.Value headers := components.Headers.Value assert.Len(t, headers, 1) @@ -359,6 +399,7 @@ func TestCreateDocument_Components_Headers(t *testing.T) { } func TestCreateDocument_Components_Links(t *testing.T) { + initTest() components := doc.Components.Value links := components.Links.Value assert.Len(t, links, 2) @@ -373,6 +414,7 @@ func TestCreateDocument_Components_Links(t *testing.T) { } func TestCreateDocument_Doc_Security(t *testing.T) { + initTest() security := doc.Security.Value assert.NotNil(t, security) assert.Len(t, security.ValueRequirements, 1) @@ -382,6 +424,7 @@ func TestCreateDocument_Doc_Security(t *testing.T) { } func TestCreateDocument_Callbacks(t *testing.T) { + initTest() callbacks := doc.Components.Value.Callbacks.Value assert.Len(t, callbacks, 1) @@ -396,6 +439,7 @@ func TestCreateDocument_Callbacks(t *testing.T) { } func TestCreateDocument_Component_Discriminator(t *testing.T) { + initTest() components := doc.Components.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) { + initTest() components := doc.Components.Value d := components.FindSchema("Dressing") 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) { + initTest() components := doc.Components.Value d := components.FindSchema("Drink") assert.NotNil(t, d.Value.AdditionalProperties.Value) diff --git a/datamodel/low/3.0/media_type.go b/datamodel/low/3.0/media_type.go index 1e3b719..64128ae 100644 --- a/datamodel/low/3.0/media_type.go +++ b/datamodel/low/3.0/media_type.go @@ -4,78 +4,78 @@ package v3 import ( - "github.com/pb33f/libopenapi/datamodel/low" - "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/utils" - "gopkg.in/yaml.v3" + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/utils" + "gopkg.in/yaml.v3" ) type MediaType struct { - Schema low.NodeReference[*Schema] - Example low.NodeReference[any] - Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]] - Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]] - Extensions map[low.KeyReference[string]]low.ValueReference[any] + Schema low.NodeReference[*Schema] + Example low.NodeReference[any] + Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]] + Encoding low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]] + Extensions map[low.KeyReference[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] { - 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] { - 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] { - return mt.Examples.Value + return mt.Examples.Value } 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. - _, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content) - if expNode != nil { - mt.Example = low.NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode} - } + // handle example if set. + _, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content) + if expNode != nil { + mt.Example = low.NodeReference[any]{Value: expNode.Value, KeyNode: expLabel, ValueNode: expNode} + } - // handle schema - sch, sErr := ExtractSchema(root, idx) - if sErr != nil { - return sErr - } - if sch != nil { - mt.Schema = *sch - } + //handle schema + sch, sErr := ExtractSchema(root, idx) + if sErr != nil { + return sErr + } + if sch != nil { + mt.Schema = *sch + } - // handle examples if set. - exps, expsL, expsN, eErr := low.ExtractMapFlat[*Example](ExamplesLabel, root, idx) - if eErr != nil { - return eErr - } - if exps != nil { - mt.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]{ - Value: exps, - KeyNode: expsL, - ValueNode: expsN, - } - } + // handle examples if set. + exps, expsL, expsN, eErr := low.ExtractMapFlat[*Example](ExamplesLabel, root, idx) + if eErr != nil { + return eErr + } + if exps != nil { + mt.Examples = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]{ + Value: exps, + KeyNode: expsL, + ValueNode: expsN, + } + } - // handle encoding - encs, encsL, encsN, encErr := low.ExtractMapFlat[*Encoding](EncodingLabel, root, idx) - if encErr != nil { - return encErr - } - if encs != nil { - mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{ - Value: encs, - KeyNode: encsL, - ValueNode: encsN, - } - } - return nil + // handle encoding + encs, encsL, encsN, encErr := low.ExtractMapFlat[*Encoding](EncodingLabel, root, idx) + if encErr != nil { + return encErr + } + if encs != nil { + mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{ + Value: encs, + KeyNode: encsL, + ValueNode: encsN, + } + } + return nil } diff --git a/datamodel/low/3.0/path.go b/datamodel/low/3.0/path.go index 88c44de..9a7112f 100644 --- a/datamodel/low/3.0/path.go +++ b/datamodel/low/3.0/path.go @@ -13,84 +13,110 @@ import ( ) const ( - PathsLabel = "paths" - GetLabel = "get" - PostLabel = "post" - PatchLabel = "patch" - PutLabel = "put" - DeleteLabel = "delete" - OptionsLabel = "options" - HeadLabel = "head" - TraceLabel = "trace" + PathsLabel = "paths" + GetLabel = "get" + PostLabel = "post" + PatchLabel = "patch" + PutLabel = "put" + DeleteLabel = "delete" + OptionsLabel = "options" + HeadLabel = "head" + TraceLabel = "trace" ) type Paths struct { - PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem] - Extensions map[low.KeyReference[string]]low.ValueReference[any] + PathItems map[low.KeyReference[string]]low.ValueReference[*PathItem] + Extensions map[low.KeyReference[string]]low.ValueReference[any] } func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] { - for k, p := range p.PathItems { - if k.Value == path { - return &p - } - } - return nil + for k, p := range p.PathItems { + if k.Value == path { + return &p + } + } + return nil } 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 { - p.Extensions = low.ExtractExtensions(root) - skip := false - var currentNode *yaml.Node + p.Extensions = low.ExtractExtensions(root) + skip := false + 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 { - if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") { - skip = true - continue - } - if skip { - skip = false - continue - } - if i%2 == 0 { - currentNode = pathNode - continue - } + // build each new path, in a new thread. + type pathBuildResult struct { + k low.KeyReference[string] + v low.ValueReference[*PathItem] + } - if ok, _, _ := utils.IsNodeRefValue(pathNode); ok { - r := low.LocateRefNode(pathNode, idx) - if r != nil { - pathNode = r - } else { - return fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d", - pathNode.Content[1].Value, pathNode.Content[1].Line, pathNode.Content[1].Column) - } - } + bChan := make(chan pathBuildResult) + eChan := make(chan error) + var buildPathItem = func(cNode, pNode *yaml.Node, b chan<- pathBuildResult, e chan<- error) { + if ok, _, _ := utils.IsNodeRefValue(pNode); ok { + r := low.LocateRefNode(pNode, idx) + if r != nil { + 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{} - _ = low.BuildModel(pathNode, &path) - err := path.Build(pathNode, idx) - if err != nil { - return err - } + path := new(PathItem) + _ = low.BuildModel(pNode, path) + err := path.Build(pNode, idx) + if err != nil { + 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 - pathsMap[low.KeyReference[string]{ - Value: currentNode.Value, - KeyNode: currentNode, - }] = low.ValueReference[*PathItem]{ - Value: &path, - ValueNode: pathNode, - } - } + pathCount := 0 + for i, pathNode := range root.Content { + if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") { + skip = true + continue + } + if skip { + skip = false + continue + } + if i%2 == 0 { + currentNode = pathNode + continue + } + pathCount++ + go buildPathItem(currentNode, pathNode, bChan, eChan) + } - p.PathItems = pathsMap - return nil + completedItems := 0 + for completedItems < pathCount { + select { + case err := <-eChan: + return err + case res := <-bChan: + completedItems++ + pathsMap[res.k] = res.v + } + } + p.PathItems = pathsMap + return nil } diff --git a/datamodel/low/3.0/path_item.go b/datamodel/low/3.0/path_item.go index f579b31..471b657 100644 --- a/datamodel/low/3.0/path_item.go +++ b/datamodel/low/3.0/path_item.go @@ -174,17 +174,13 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error { } n := 0 -allDone: - for { + total := len(ops) + for n < total { select { case buildError := <-opErrorChan: return buildError case <-opBuildChan: n++ - if n == len(ops) { - break allDone - } - } } diff --git a/datamodel/low/3.0/response.go b/datamodel/low/3.0/response.go index 7aff227..6817015 100644 --- a/datamodel/low/3.0/response.go +++ b/datamodel/low/3.0/response.go @@ -75,7 +75,7 @@ func (r *Response) FindLink(hType string) *low.ValueReference[*Link] { func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error { r.Extensions = low.ExtractExtensions(root) - // extract headers + //extract headers headers, lN, kN, err := low.ExtractMapFlat[*Header](HeadersLabel, root, idx) if err != nil { return err diff --git a/datamodel/low/3.0/schema.go b/datamodel/low/3.0/schema.go index 970655b..aba43e8 100644 --- a/datamodel/low/3.0/schema.go +++ b/datamodel/low/3.0/schema.go @@ -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 { - level++ + + if low.IsCircular(root, idx) { + return nil // circular references cannot be built. + } + 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. } - + level++ if h, _, _ := utils.IsNodeRefValue(root); h { ref := low.LocateRefNode(root, idx) 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} } + // 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 _, propLabel, propsNode := utils.FindKeyNodeFull(PropertiesLabel, root.Content) if propsNode != nil { propertyMap := make(map[low.KeyReference[string]]low.ValueReference[*Schema]) var currentProp *yaml.Node + totalProps := 0 for i, prop := range propsNode.Content { if i%2 == 0 { 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) } } - - var property Schema - _ = low.BuildModel(prop, &property) - err := property.BuildLevel(prop, idx, level) - if err != nil { + totalProps++ + go buildProperty(currentProp, prop, bChan, eChan) + } + completedProps := 0 + for completedProps < totalProps { + select { + case err := <-eChan: return err - } - propertyMap[low.KeyReference[string]{ - Value: currentProp.Value, - KeyNode: currentProp, - }] = low.ValueReference[*Schema]{ - Value: &property, - ValueNode: prop, + case res := <-bChan: + completedProps++ + propertyMap[res.k] = res.v } } 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 } +type schemaBuildResult struct { + k low.KeyReference[string] + v low.ValueReference[*Schema] +} + func (s *Schema) extractExtensions(root *yaml.Node) { s.Extensions = low.ExtractExtensions(root) } @@ -247,9 +294,9 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo } 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 { ref := low.LocateRefNode(vn, idx) 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) if err != nil { *errors = append(*errors, err) return nil } + return &low.NodeReference[*Schema]{ - Value: &schema, + Value: schema, KeyNode: kn, ValueNode: vn, } @@ -292,8 +353,8 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo } } if utils.IsNodeArray(valueNode) { + //fmt.Println("polymorphic looping sucks dude.") for _, vn := range valueNode.Content { - if h, _, _ := utils.IsNodeRefValue(vn); h { ref := low.LocateRefNode(vn, idx) if ref != nil { @@ -312,7 +373,6 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo } } - //wg.Done() return labelNode, valueNode } @@ -345,12 +405,20 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S } 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 _ = low.BuildModel(schNode, &schema) err := schema.Build(schNode, idx) + addSeenSchema(fmt.Sprintf("%d:%d", schNode.Line, schNode.Column), &schema) if err != nil { return nil, err } + return &low.NodeReference[*Schema]{Value: &schema, KeyNode: schLabel, ValueNode: schNode}, nil } return nil, nil diff --git a/datamodel/low/3.0/schema_test.go b/datamodel/low/3.0/schema_test.go index 8e4cb5b..e621045 100644 --- a/datamodel/low/3.0/schema_test.go +++ b/datamodel/low/3.0/schema_test.go @@ -9,7 +9,7 @@ import ( ) func Test_Schema(t *testing.T) { - + clearSchemas() testSpec := `type: object description: something object discriminator: @@ -221,7 +221,7 @@ additionalProperties: true ` } 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 // 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 @@ -339,7 +339,7 @@ properties: } func TestSchema_Build_ErrorAdditionalProps(t *testing.T) { - + clearSchemas() yml := `additionalProperties: $ref: #borko` @@ -357,19 +357,19 @@ func TestSchema_Build_ErrorAdditionalProps(t *testing.T) { } func TestSchema_Build_PropsLookup(t *testing.T) { - - doc := `components: + clearSchemas() + yml := `components: schemas: Something: description: this is something type: string` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object properties: aValue: $ref: '#/components/schemas/Something'` @@ -385,19 +385,19 @@ properties: } func TestSchema_Build_PropsLookup_Fail(t *testing.T) { - - doc := `components: + clearSchemas() + yml := `components: schemas: Something: description: this is something type: string` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object properties: aValue: $ref: '#/bork'` @@ -412,8 +412,8 @@ properties: } func Test_Schema_Polymorphism_Array_Ref(t *testing.T) { - - doc := `components: + clearSchemas() + yml := `components: schemas: Something: type: object @@ -425,11 +425,11 @@ func Test_Schema_Polymorphism_Array_Ref(t *testing.T) { example: anything` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object allOf: - $ref: '#/components/schemas/Something' oneOf: @@ -460,8 +460,8 @@ items: } func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) { - - doc := `components: + clearSchemas() + yml := `components: schemas: Something: type: object @@ -473,11 +473,11 @@ func Test_Schema_Polymorphism_Array_Ref_Fail(t *testing.T) { example: anything` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object allOf: - $ref: '#/components/schemas/Missing' oneOf: @@ -502,8 +502,8 @@ items: } func Test_Schema_Polymorphism_Map_Ref(t *testing.T) { - - doc := `components: + clearSchemas() + yml := `components: schemas: Something: type: object @@ -515,11 +515,11 @@ func Test_Schema_Polymorphism_Map_Ref(t *testing.T) { example: anything` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object allOf: $ref: '#/components/schemas/Something' oneOf: @@ -550,8 +550,8 @@ items: } func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) { - - doc := `components: + clearSchemas() + yml := `components: schemas: Something: type: object @@ -563,11 +563,11 @@ func Test_Schema_Polymorphism_Map_Ref_Fail(t *testing.T) { example: anything` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object allOf: $ref: '#/components/schemas/Missing' oneOf: @@ -592,18 +592,19 @@ items: } func Test_Schema_Polymorphism_BorkParent(t *testing.T) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: $ref: #borko` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object allOf: $ref: '#/components/schemas/Something'` @@ -620,18 +621,19 @@ allOf: } func Test_Schema_Polymorphism_BorkChild(t *testing.T) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: $ref: #borko` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object allOf: $ref: #borko` @@ -648,8 +650,9 @@ allOf: } func Test_Schema_Polymorphism_RefMadness(t *testing.T) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: $ref: '#/components/schemas/Else' @@ -657,11 +660,11 @@ func Test_Schema_Polymorphism_RefMadness(t *testing.T) { description: madness` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object allOf: $ref: '#/components/schemas/Something'` @@ -681,8 +684,9 @@ allOf: } func Test_Schema_Polymorphism_RefMadnessBork(t *testing.T) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: $ref: '#/components/schemas/Else' @@ -690,11 +694,11 @@ func Test_Schema_Polymorphism_RefMadnessBork(t *testing.T) { $ref: #borko` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `type: object + yml = `type: object allOf: $ref: '#/components/schemas/Something'` @@ -711,10 +715,11 @@ allOf: } func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) { + clearSchemas() // this does not work, but it won't error out. - doc := `components: + yml := `components: schemas: Something: $ref: '#/components/schemas/Else' @@ -722,11 +727,11 @@ func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) { description: hey!` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `$ref: '#/components/schemas/Something'` + yml = `$ref: '#/components/schemas/Something'` var sch Schema var idxNode yaml.Node @@ -741,19 +746,20 @@ func Test_Schema_Polymorphism_RefMadnessIllegal(t *testing.T) { } func TestExtractSchema(t *testing.T) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: description: this is something type: string` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `schema: + yml = `schema: type: object properties: aValue: @@ -765,23 +771,25 @@ func TestExtractSchema(t *testing.T) { res, err := ExtractSchema(idxNode.Content[0], idx) assert.NoError(t, err) 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) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: description: this is something type: string` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `schema: + yml = `schema: $ref: '#/components/schemas/Something'` var idxNode yaml.Node @@ -794,19 +802,20 @@ func TestExtractSchema_Ref(t *testing.T) { } func TestExtractSchema_Ref_Fail(t *testing.T) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: description: this is something type: string` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `schema: + yml = `schema: $ref: '#/components/schemas/Missing'` var idxNode yaml.Node @@ -817,19 +826,20 @@ func TestExtractSchema_Ref_Fail(t *testing.T) { } func TestExtractSchema_RefRoot(t *testing.T) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: description: this is something type: string` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `$ref: '#/components/schemas/Something'` + yml = `$ref: '#/components/schemas/Something'` var idxNode yaml.Node _ = yaml.Unmarshal([]byte(yml), &idxNode) @@ -841,19 +851,20 @@ func TestExtractSchema_RefRoot(t *testing.T) { } func TestExtractSchema_RefRoot_Fail(t *testing.T) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: description: this is something type: string` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `$ref: '#/components/schemas/Missing'` + yml = `$ref: '#/components/schemas/Missing'` var idxNode yaml.Node _ = yaml.Unmarshal([]byte(yml), &idxNode) @@ -864,18 +875,19 @@ func TestExtractSchema_RefRoot_Fail(t *testing.T) { } func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) { + clearSchemas() - doc := `components: + yml := `components: schemas: Something: $ref: #bork` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `$ref: '#/components/schemas/Something'` + yml = `$ref: '#/components/schemas/Something'` var idxNode yaml.Node _ = yaml.Unmarshal([]byte(yml), &idxNode) @@ -887,17 +899,19 @@ func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) { func TestExtractSchema_DoNothing(t *testing.T) { - doc := `components: + clearSchemas() + + yml := `components: schemas: Something: $ref: #bork` var iNode yaml.Node - mErr := yaml.Unmarshal([]byte(doc), &iNode) + mErr := yaml.Unmarshal([]byte(yml), &iNode) assert.NoError(t, mErr) idx := index.NewSpecIndex(&iNode) - yml := `please: do nothing.` + yml = `please: do nothing.` var idxNode yaml.Node _ = yaml.Unmarshal([]byte(yml), &idxNode) diff --git a/datamodel/low/extraction_functions.go b/datamodel/low/extraction_functions.go index 38338f6..d490b57 100644 --- a/datamodel/low/extraction_functions.go +++ b/datamodel/low/extraction_functions.go @@ -285,6 +285,11 @@ func ExtractMapFlatNoLookup[PT Buildable[N], N any](root *yaml.Node, idx *index. 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) { var labelNode, valueNode *yaml.Node 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 { var currentLabelNode *yaml.Node 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 { if i%2 == 0 { currentLabelNode = en continue } - // check our valueNode isn't a reference still. if h, _, _ := utils.IsNodeRefValue(en); h { 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-") { continue // yo, don't pay any attention to extensions, not here anyway. } - var n PT = new(N) - err := BuildModel(en, n) - if err != nil { - return nil, labelNode, valueNode, err - } - berr := n.Build(en, idx) - if berr != nil { - return nil, labelNode, valueNode, berr - } - valueMap[KeyReference[string]{ - Value: currentLabelNode.Value, - KeyNode: currentLabelNode, - }] = ValueReference[PT]{ - Value: n, - ValueNode: en, + totalKeys++ + go buildMap(currentLabelNode, en, bChan, eChan) + } + + completedKeys := 0 + for completedKeys < totalKeys { + select { + case err := <-eChan: + return valueMap, labelNode, valueNode, err + case res := <-bChan: + completedKeys++ + valueMap[res.k] = res.v } } return valueMap, labelNode, valueNode, nil diff --git a/datamodel/low/reference.go b/datamodel/low/reference.go index b592a70..ef85d05 100644 --- a/datamodel/low/reference.go +++ b/datamodel/low/reference.go @@ -54,3 +54,16 @@ func (n KeyReference[T]) IsEmpty() bool { func (n KeyReference[T]) GenerateMapKey() string { 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 +} diff --git a/resolver/resolver_test.go b/resolver/resolver_test.go index 87118c5..9066da6 100644 --- a/resolver/resolver_test.go +++ b/resolver/resolver_test.go @@ -1,89 +1,89 @@ package resolver import ( - "github.com/pb33f/libopenapi/index" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" - "io/ioutil" - "testing" + "github.com/pb33f/libopenapi/index" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "io/ioutil" + "testing" ) func TestNewResolver(t *testing.T) { - assert.Nil(t, NewResolver(nil)) + assert.Nil(t, NewResolver(nil)) } func Benchmark_ResolveDocumentStripe(b *testing.B) { - stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") - for n := 0; n < b.N; n++ { - var rootNode yaml.Node - yaml.Unmarshal(stripe, &rootNode) - index := index.NewSpecIndex(&rootNode) - resolver := NewResolver(index) - resolver.Resolve() - } + stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") + for n := 0; n < b.N; n++ { + var rootNode yaml.Node + yaml.Unmarshal(stripe, &rootNode) + index := index.NewSpecIndex(&rootNode) + resolver := NewResolver(index) + resolver.Resolve() + } } func TestResolver_ResolveComponents_CircularSpec(t *testing.T) { - circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml") - var rootNode yaml.Node - yaml.Unmarshal(circular, &rootNode) + circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml") + var rootNode yaml.Node + yaml.Unmarshal(circular, &rootNode) - index := index.NewSpecIndex(&rootNode) + index := index.NewSpecIndex(&rootNode) - resolver := NewResolver(index) - assert.NotNil(t, resolver) + resolver := NewResolver(index) + assert.NotNil(t, resolver) - circ := resolver.Resolve() - assert.Len(t, circ, 3) + circ := resolver.Resolve() + assert.Len(t, circ, 4) - _, err := yaml.Marshal(resolver.resolvedRoot) - assert.NoError(t, err) + _, err := yaml.Marshal(resolver.resolvedRoot) + assert.NoError(t, err) } func TestResolver_ResolveComponents_Stripe(t *testing.T) { - stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") - var rootNode yaml.Node - yaml.Unmarshal(stripe, &rootNode) + stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") + var rootNode yaml.Node + yaml.Unmarshal(stripe, &rootNode) - index := index.NewSpecIndex(&rootNode) + index := index.NewSpecIndex(&rootNode) - resolver := NewResolver(index) - assert.NotNil(t, resolver) + resolver := NewResolver(index) + assert.NotNil(t, resolver) - circ := resolver.Resolve() - assert.Len(t, circ, 0) + circ := resolver.Resolve() + assert.Len(t, circ, 229) } func TestResolver_ResolveComponents_MixedRef(t *testing.T) { - mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml") - var rootNode yaml.Node - yaml.Unmarshal(mixedref, &rootNode) + mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml") + var rootNode yaml.Node + yaml.Unmarshal(mixedref, &rootNode) - index := index.NewSpecIndex(&rootNode) + index := index.NewSpecIndex(&rootNode) - resolver := NewResolver(index) - assert.NotNil(t, resolver) + resolver := NewResolver(index) + assert.NotNil(t, resolver) - circ := resolver.Resolve() - assert.Len(t, circ, 2) + circ := resolver.Resolve() + assert.Len(t, circ, 2) } func TestResolver_ResolveComponents_k8s(t *testing.T) { - k8s, _ := ioutil.ReadFile("../test_specs/k8s.json") - var rootNode yaml.Node - yaml.Unmarshal(k8s, &rootNode) + k8s, _ := ioutil.ReadFile("../test_specs/k8s.json") + var rootNode yaml.Node + yaml.Unmarshal(k8s, &rootNode) - index := index.NewSpecIndex(&rootNode) + index := index.NewSpecIndex(&rootNode) - resolver := NewResolver(index) - assert.NotNil(t, resolver) + resolver := NewResolver(index) + assert.NotNil(t, resolver) - circ := resolver.Resolve() - assert.Len(t, circ, 0) + circ := resolver.Resolve() + assert.Len(t, circ, 1) }