diff --git a/datamodel/low/3.0/components.go b/datamodel/low/3.0/components.go index 0c6cacc..1e2b52a 100644 --- a/datamodel/low/3.0/components.go +++ b/datamodel/low/3.0/components.go @@ -1,19 +1,174 @@ package v3 import ( - "gopkg.in/yaml.v3" - "net/http" + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/utils" + "gopkg.in/yaml.v3" +) + +const ( + ComponentsLabel = "components" + SchemasLabel = "schemas" ) type Components struct { - Node *yaml.Node - Schemas map[string]Schema - Responses map[string]Response - Parameters map[string]Parameter - Examples map[string]Example - RequestBodies map[string]RequestBody - Headers map[string]http.Header - SecuritySchemes map[string]SecurityScheme - Links map[string]Link - Callbacks map[string]Callback + Schemas low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]] + Responses low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]] + Parameters low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Parameter]] + Examples low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]] + RequestBodies low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*RequestBody]] + Headers low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]] + SecuritySchemes low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SecurityScheme]] + Links low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Link]] + Callbacks low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Callback]] + Extensions map[low.KeyReference[string]]low.ValueReference[any] +} + +func (co *Components) FindSchema(schema string) *low.ValueReference[*Schema] { + return FindItemInMap[*Schema](schema, co.Schemas.Value) +} + +func (co *Components) FindResponse(response string) *low.ValueReference[*Response] { + return FindItemInMap[*Response](response, co.Responses.Value) +} + +func (co *Components) Build(root *yaml.Node) error { + extensionMap, err := ExtractExtensions(root) + if err != nil { + return err + } + co.Extensions = extensionMap + + // build out components asynchronously for speed. There could be some significant weight here. + skipChan := make(chan bool) + errorChan := make(chan error) + paramChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Parameter]]) + schemaChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Schema]]) + responsesChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Response]]) + examplesChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Example]]) + requestBodiesChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*RequestBody]]) + headersChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Header]]) + securitySchemesChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SecurityScheme]]) + linkChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Link]]) + callbackChan := make(chan low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Callback]]) + + go extractComponentValues[*Schema](SchemasLabel, root, skipChan, errorChan, schemaChan) + go extractComponentValues[*Parameter](ParametersLabel, root, skipChan, errorChan, paramChan) + go extractComponentValues[*Response](ResponsesLabel, root, skipChan, errorChan, responsesChan) + go extractComponentValues[*Example](ExamplesLabel, root, skipChan, errorChan, examplesChan) + go extractComponentValues[*RequestBody](RequestBodiesLabel, root, skipChan, errorChan, requestBodiesChan) + go extractComponentValues[*Header](HeadersLabel, root, skipChan, errorChan, headersChan) + go extractComponentValues[*SecurityScheme](SecuritySchemesLabel, root, skipChan, errorChan, securitySchemesChan) + go extractComponentValues[*Link](LinksLabel, root, skipChan, errorChan, linkChan) + go extractComponentValues[*Callback](CallbacksLabel, root, skipChan, errorChan, callbackChan) + + n := 0 + total := 9 +allDone: + for { + select { + case buildError := <-errorChan: + return buildError + case <-skipChan: + n++ + if n == total { + break allDone + } + case params := <-paramChan: + co.Parameters = params + n++ + if n == total { + break allDone + } + case schemas := <-schemaChan: + co.Schemas = schemas + n++ + if n == total { + break allDone + } + case responses := <-responsesChan: + co.Responses = responses + n++ + if n == total { + break allDone + } + case examples := <-examplesChan: + co.Examples = examples + n++ + if n == total { + break allDone + } + case reqBody := <-requestBodiesChan: + co.RequestBodies = reqBody + n++ + if n == total { + break allDone + } + case headers := <-headersChan: + co.Headers = headers + n++ + if n == total { + break allDone + } + case sScheme := <-securitySchemesChan: + co.SecuritySchemes = sScheme + n++ + if n == total { + break allDone + } + case links := <-linkChan: + co.Links = links + n++ + if n == total { + break allDone + } + case callbacks := <-callbackChan: + co.Callbacks = callbacks + n++ + if n == total { + break allDone + } + } + } + + return nil +} + +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]]) { + _, nodeLabel, nodeValue := utils.FindKeyNodeFull(label, root.Content) + if nodeValue == nil { + skip <- true + return + } + var currentLabel *yaml.Node + componentValues := make(map[low.KeyReference[string]]low.ValueReference[T]) + for i, v := range nodeValue.Content { + if i%2 == 0 { + currentLabel = v + continue + } + var n T = new(N) + err := BuildModel(v, n) + if err != nil { + errorChan <- err + } + err = n.Build(v) + if err != nil { + errorChan <- err + } + componentValues[low.KeyReference[string]{ + KeyNode: currentLabel, + Value: currentLabel.Value, + }] = low.ValueReference[T]{ + Value: n, + ValueNode: v, + } + } + results := low.NodeReference[map[low.KeyReference[string]]low.ValueReference[T]]{ + KeyNode: nodeLabel, + ValueNode: nodeValue, + Value: componentValues, + } + resultChan <- results } diff --git a/datamodel/low/3.0/extraction_functions.go b/datamodel/low/3.0/extraction_functions.go index 2de03e1..656a6f0 100644 --- a/datamodel/low/3.0/extraction_functions.go +++ b/datamodel/low/3.0/extraction_functions.go @@ -37,7 +37,7 @@ func ExtractSchema(root *yaml.Node) (*low.NodeReference[*Schema], error) { if err != nil { return nil, err } - err = schema.Build(schNode, 0) + err = schema.Build(schNode) if err != nil { return nil, err } diff --git a/datamodel/low/3.0/operation.go b/datamodel/low/3.0/operation.go index 6586a2c..0c58095 100644 --- a/datamodel/low/3.0/operation.go +++ b/datamodel/low/3.0/operation.go @@ -6,10 +6,11 @@ import ( ) const ( - ParametersLabel = "parameters" - RequestBodyLabel = "requestBody" - ResponsesLabel = "responses" - CallbacksLabel = "callbacks" + ParametersLabel = "parameters" + RequestBodyLabel = "requestBody" + RequestBodiesLabel = "requestBodies" + ResponsesLabel = "responses" + CallbacksLabel = "callbacks" ) type Operation struct { diff --git a/datamodel/low/3.0/path.go b/datamodel/low/3.0/path.go index 88851fb..ffb3f91 100644 --- a/datamodel/low/3.0/path.go +++ b/datamodel/low/3.0/path.go @@ -189,9 +189,11 @@ allDone: case buildError := <-opErrorChan: return buildError case <-opBuildChan: - if n == len(ops)-1 { + n++ + if n == len(ops) { break allDone } + } } diff --git a/datamodel/low/3.0/schema.go b/datamodel/low/3.0/schema.go index b2e8b4d..4bcea4e 100644 --- a/datamodel/low/3.0/schema.go +++ b/datamodel/low/3.0/schema.go @@ -61,7 +61,11 @@ func (s *Schema) FindProperty(name string) *low.ValueReference[*Schema] { return FindItemInMap[*Schema](name, s.Properties.Value) } -func (s *Schema) Build(root *yaml.Node, level int) error { +func (s *Schema) Build(root *yaml.Node) error { + return s.BuildLevel(root, 0) +} + +func (s *Schema) BuildLevel(root *yaml.Node, level int) error { level++ if level > 50 { return nil // we're done, son! too fricken deep. @@ -152,7 +156,7 @@ func (s *Schema) Build(root *yaml.Node, level int) error { if err != nil { return err } - err = property.Build(prop, level) + err = property.BuildLevel(prop, level) if err != nil { return err } @@ -245,7 +249,7 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo *errors = append(*errors, err) return nil } - err = schema.Build(vn, level) + err = schema.BuildLevel(vn, level) if err != nil { *errors = append(*errors, err) return nil diff --git a/datamodel/low/3.0/schema_test.go b/datamodel/low/3.0/schema_test.go index 41eca7b..bf817a8 100644 --- a/datamodel/low/3.0/schema_test.go +++ b/datamodel/low/3.0/schema_test.go @@ -111,7 +111,7 @@ additionalProperties: true ` mbErr := BuildModel(&rootNode, &sch) assert.NoError(t, mbErr) - schErr := sch.Build(rootNode.Content[0], 0) + schErr := sch.Build(rootNode.Content[0]) assert.NoError(t, schErr) assert.Equal(t, "something object", sch.Description.Value) assert.True(t, sch.AdditionalProperties.Value.(bool)) diff --git a/datamodel/low/3.0/security_scheme.go b/datamodel/low/3.0/security_scheme.go index 9ac8a25..d2324b1 100644 --- a/datamodel/low/3.0/security_scheme.go +++ b/datamodel/low/3.0/security_scheme.go @@ -7,7 +7,8 @@ import ( ) const ( - SecurityLabel = "security" + SecurityLabel = "security" + SecuritySchemesLabel = "securitySchemes" ) type SecurityScheme struct { @@ -47,39 +48,7 @@ func (sr *SecurityRequirement) FindRequirement(name string) []low.ValueReference } func (sr *SecurityRequirement) Build(root *yaml.Node) error { - - //if utils.IsNodeArray(root) { - // var currSec *yaml.Node - // var requirements []low.ValueReference[map[low.KeyReference[string]][]low.ValueReference[string]] - // for i, n := range root.Content { - // if i%2 == 0 { - // currSec = n - // continue - // } - // if utils.IsNodeArray(n) { - // res := make(map[low.KeyReference[string]][]low.ValueReference[string]) - // var dat []low.ValueReference[string] - // for _, r := range n.Content { - // dat = append(dat, low.ValueReference[string]{ - // Value: r.Value, - // ValueNode: r, - // }) - // } - // res[low.KeyReference[string]{ - // Value: currSec.Value, - // KeyNode: currSec, - // }] = dat - // requirements = append(requirements, low.ValueReference[map[low.KeyReference[string]][]low.ValueReference[string]]{ - // Value: res, - // ValueNode: n, - // }) - // } - // } - // sr.Value = requirements - //} - if utils.IsNodeArray(root) { - var requirements []low.ValueReference[map[low.KeyReference[string]][]low.ValueReference[string]] for _, n := range root.Content { var currSec *yaml.Node diff --git a/openapi/create_document.go b/openapi/create_document.go index 3040b67..1c423a7 100644 --- a/openapi/create_document.go +++ b/openapi/create_document.go @@ -39,6 +39,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*v3.Document, error) { extractServers, extractTags, extractPaths, + extractComponents, } wg.Add(len(extractionFuncs)) for _, f := range extractionFuncs { @@ -68,6 +69,21 @@ func extractInfo(info *datamodel.SpecInfo, doc *v3.Document) error { return nil } +func extractComponents(info *datamodel.SpecInfo, doc *v3.Document) error { + _, ln, vn := utils.FindKeyNodeFull(v3.ComponentsLabel, info.RootNode.Content) + if vn != nil { + ir := v3.Components{} + err := v3.BuildModel(vn, &ir) + if err != nil { + return err + } + err = ir.Build(vn) + nr := low.NodeReference[*v3.Components]{Value: &ir, ValueNode: vn, KeyNode: ln} + doc.Components = nr + } + return nil +} + func extractServers(info *datamodel.SpecInfo, doc *v3.Document) error { _, ln, vn := utils.FindKeyNodeFull(v3.ServersLabel, info.RootNode.Content) if vn != nil { diff --git a/openapi/create_document_test.go b/openapi/create_document_test.go index bdfe217..6afedff 100644 --- a/openapi/create_document_test.go +++ b/openapi/create_document_test.go @@ -235,3 +235,26 @@ func TestCreateDocument_Paths(t *testing.T) { assert.Equal(t, "https://pb33f.io", servers[0].Value.URL.Value) } + +func TestCreateDocument_Components_Schemas(t *testing.T) { + + components := doc.Components.Value + assert.NotNil(t, components) + assert.Len(t, components.Schemas.Value, 5) + + burger := components.FindSchema("Burger") + assert.NotNil(t, burger.Value) + assert.Equal(t, "The tastiest food on the planet you would love to eat everyday", burger.Value.Description.Value) + + er := components.FindSchema("Error") + assert.NotNil(t, er.Value) + assert.Equal(t, "Error defining what went wrong when providing a specification. The message should help indicate the issue clearly.", er.Value.Description.Value) + + fries := components.FindSchema("Fries") + assert.NotNil(t, fries.Value) + + assert.Len(t, fries.Value.Properties.Value, 3) + assert.Equal(t, "a frosty cold beverage can be coke or sprite", + fries.Value.FindProperty("favoriteDrink").Value.Description.Value) + +}