diff --git a/datamodel/low/3.0/callback.go b/datamodel/low/3.0/callback.go index 054ca6d..1bd8f12 100644 --- a/datamodel/low/3.0/callback.go +++ b/datamodel/low/3.0/callback.go @@ -2,6 +2,8 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -14,22 +16,28 @@ func (cb *Callback) FindExpression(exp string) *low.ValueReference[*PathItem] { return FindItemInMap[*PathItem](exp, cb.Expression.Value) } -func (cb *Callback) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - cb.Extensions = extensionMap +func (cb *Callback) Build(root *yaml.Node, idx *index.SpecIndex) error { + cb.Extensions = ExtractExtensions(root) // handle callback var currentCB *yaml.Node callbacks := make(map[low.KeyReference[string]]low.ValueReference[*PathItem]) + + if ok, _, _ := utils.IsNodeRefValue(root); ok { + r := LocateRefNode(root, idx) + if r != nil { + root = r + } else { + return nil + } + } + for i, callbackNode := range root.Content { if i%2 == 0 { currentCB = callbackNode continue } - callback, eErr := ExtractObjectRaw[*PathItem](callbackNode) + callback, eErr := ExtractObjectRaw[*PathItem](callbackNode, idx) if eErr != nil { return eErr } diff --git a/datamodel/low/3.0/callback_test.go b/datamodel/low/3.0/callback_test.go new file mode 100644 index 0000000..262c477 --- /dev/null +++ b/datamodel/low/3.0/callback_test.go @@ -0,0 +1,36 @@ +package v3 + +import ( + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "testing" +) + +func TestCallback_Build_Success(t *testing.T) { + + yml := `'{$request.query.queryUrl}': + post: + requestBody: + description: Callback payload + content: + 'application/json': + schema: + type: string + responses: + '200': + description: callback successfully processed` + + var rootNode yaml.Node + mErr := yaml.Unmarshal([]byte(yml), &rootNode) + assert.NoError(t, mErr) + + var n Callback + err := BuildModel(rootNode.Content[0], &n) + assert.NoError(t, err) + + err = n.Build(rootNode.Content[0], nil) + assert.NoError(t, err) + + assert.Len(t, n.Expression.Value, 1) + +} diff --git a/datamodel/low/3.0/components.go b/datamodel/low/3.0/components.go index fe723f7..462dc96 100644 --- a/datamodel/low/3.0/components.go +++ b/datamodel/low/3.0/components.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -56,12 +57,8 @@ func (co *Components) FindCallback(callback string) *low.ValueReference[*Callbac return FindItemInMap[*Callback](callback, co.Callbacks.Value) } -func (co *Components) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - co.Extensions = extensionMap +func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error { + co.Extensions = ExtractExtensions(root) // build out components asynchronously for speed. There could be some significant weight here. skipChan := make(chan bool) @@ -76,15 +73,15 @@ func (co *Components) Build(root *yaml.Node) error { 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) + go extractComponentValues[*Schema](SchemasLabel, root, skipChan, errorChan, schemaChan, idx) + go extractComponentValues[*Parameter](ParametersLabel, root, skipChan, errorChan, paramChan, idx) + go extractComponentValues[*Response](ResponsesLabel, root, skipChan, errorChan, responsesChan, idx) + go extractComponentValues[*Example](ExamplesLabel, root, skipChan, errorChan, examplesChan, idx) + go extractComponentValues[*RequestBody](RequestBodiesLabel, root, skipChan, errorChan, requestBodiesChan, idx) + go extractComponentValues[*Header](HeadersLabel, root, skipChan, errorChan, headersChan, idx) + go extractComponentValues[*SecurityScheme](SecuritySchemesLabel, root, skipChan, errorChan, securitySchemesChan, idx) + go extractComponentValues[*Link](LinksLabel, root, skipChan, errorChan, linkChan, idx) + go extractComponentValues[*Callback](CallbacksLabel, root, skipChan, errorChan, callbackChan, idx) n := 0 total := 9 @@ -159,7 +156,7 @@ allDone: } 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]]) { + 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) if nodeValue == nil { skip <- true @@ -177,7 +174,7 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml. if err != nil { errorChan <- err } - err = n.Build(v) + err = n.Build(v, idx) if err != nil { errorChan <- err } diff --git a/datamodel/low/3.0/encoding.go b/datamodel/low/3.0/encoding.go index 4a21955..7ccab40 100644 --- a/datamodel/low/3.0/encoding.go +++ b/datamodel/low/3.0/encoding.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" ) @@ -21,9 +22,9 @@ func (en *Encoding) FindHeader(hType string) *low.ValueReference[*Header] { return FindItemInMap[*Header](hType, en.Headers.Value) } -func (en *Encoding) Build(root *yaml.Node) error { +func (en *Encoding) Build(root *yaml.Node, idx *index.SpecIndex) error { - headers, hL, hN, err := ExtractMapFlat[*Header](HeadersLabel, root) + headers, hL, hN, err := ExtractMapFlat[*Header](HeadersLabel, root, idx) if err != nil { return err } diff --git a/datamodel/low/3.0/example.go b/datamodel/low/3.0/example.go index e020e01..3002588 100644 --- a/datamodel/low/3.0/example.go +++ b/datamodel/low/3.0/example.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -20,19 +21,14 @@ type Example struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } -func (ex *Example) Build(root *yaml.Node) error { - // extract extensions - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - ex.Extensions = extensionMap +func (ex *Example) Build(root *yaml.Node, idx *index.SpecIndex) error { + ex.Extensions = ExtractExtensions(root) // extract value _, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content) if vn != nil { var n map[string]interface{} - err = vn.Decode(&n) + err := vn.Decode(&n) if err != nil { return err } diff --git a/datamodel/low/3.0/external_doc.go b/datamodel/low/3.0/external_doc.go index 5dd4d32..dea0958 100644 --- a/datamodel/low/3.0/external_doc.go +++ b/datamodel/low/3.0/external_doc.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" ) @@ -11,12 +12,7 @@ type ExternalDoc struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } -func (ex *ExternalDoc) Build(root *yaml.Node) error { - // extract extensions - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - ex.Extensions = extensionMap +func (ex *ExternalDoc) Build(root *yaml.Node, idx *index.SpecIndex) error { + ex.Extensions = ExtractExtensions(root) return nil } diff --git a/datamodel/low/3.0/extraction_functions.go b/datamodel/low/3.0/extraction_functions.go index 5d59754..136864f 100644 --- a/datamodel/low/3.0/extraction_functions.go +++ b/datamodel/low/3.0/extraction_functions.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" "strconv" @@ -9,6 +10,13 @@ import ( "sync" ) +var KnownSchemas map[string]low.NodeReference[*Schema] + +func init() { + KnownSchemas = make(map[string]low.NodeReference[*Schema]) + +} + func FindItemInMap[T any](item string, collection map[low.KeyReference[string]]low.ValueReference[T]) *low.ValueReference[T] { for n, o := range collection { if n.Value == item { @@ -18,15 +26,34 @@ func FindItemInMap[T any](item string, collection map[low.KeyReference[string]]l return nil } -func ExtractSchema(root *yaml.Node) (*low.NodeReference[*Schema], error) { - _, schLabel, schNode := utils.FindKeyNodeFull(SchemaLabel, root.Content) +func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*Schema], error) { + var schLabel, schNode *yaml.Node + if rf, rl, _ := utils.IsNodeRefValue(root); rf { + // locate reference in index. + ref := LocateRefNode(root, idx) + if ref != nil { + schNode = ref + schLabel = rl + } + } else { + _, schLabel, schNode = utils.FindKeyNodeFull(SchemaLabel, root.Content) + if schNode != nil { + if h, _, _ := utils.IsNodeRefValue(schNode); h { + ref := LocateRefNode(schNode, idx) + if ref != nil { + schNode = ref + } + } + } + } + if schNode != nil { var schema Schema err := BuildModel(schNode, &schema) if err != nil { return nil, err } - err = schema.Build(schNode) + err = schema.Build(schNode, idx) if err != nil { return nil, err } @@ -37,21 +64,49 @@ func ExtractSchema(root *yaml.Node) (*low.NodeReference[*Schema], error) { var mapLock sync.Mutex -func ExtractObjectRaw[T low.Buildable[N], N any](root *yaml.Node) (T, error) { +func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) *yaml.Node { + if rf, _, rv := utils.IsNodeRefValue(root); rf { + found := idx.GetMappedReferences() + if found != nil && found[rv] != nil { + return found[rv].Node + } + } + return nil +} + +func ExtractObjectRaw[T low.Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (T, error) { var n T = new(N) err := BuildModel(root, n) if err != nil { return n, err } - err = n.Build(root) + err = n.Build(root, idx) if err != nil { return n, err } return n, nil } -func ExtractObject[T low.Buildable[N], N any](label string, root *yaml.Node) (low.NodeReference[T], error) { - _, ln, vn := utils.FindKeyNodeFull(label, root.Content) +func ExtractObject[T low.Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (low.NodeReference[T], error) { + var ln, vn *yaml.Node + if rf, rl, _ := utils.IsNodeRefValue(root); rf { + // locate reference in index. + ref := LocateRefNode(root, idx) + if ref != nil { + vn = ref + ln = rl + } + } else { + _, ln, vn = utils.FindKeyNodeFull(label, root.Content) + if vn != nil { + if h, _, _ := utils.IsNodeRefValue(vn); h { + ref := LocateRefNode(vn, idx) + if ref != nil { + vn = ref + } + } + } + } var n T = new(N) err := BuildModel(vn, n) if err != nil { @@ -60,7 +115,7 @@ func ExtractObject[T low.Buildable[N], N any](label string, root *yaml.Node) (lo if ln == nil { return low.NodeReference[T]{}, nil } - err = n.Build(vn) + err = n.Build(vn, idx) if err != nil { return low.NodeReference[T]{}, err } @@ -71,17 +126,41 @@ func ExtractObject[T low.Buildable[N], N any](label string, root *yaml.Node) (lo }, nil } -func ExtractArray[T low.Buildable[N], N any](label string, root *yaml.Node) ([]low.ValueReference[T], *yaml.Node, *yaml.Node, error) { - _, ln, vn := utils.FindKeyNodeFull(label, root.Content) +func ExtractArray[T low.Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) ([]low.ValueReference[T], *yaml.Node, *yaml.Node, error) { + var ln, vn *yaml.Node + if rf, rl, _ := utils.IsNodeRefValue(root); rf { + // locate reference in index. + ref := LocateRefNode(root, idx) + if ref != nil { + vn = ref + ln = rl + } + } else { + _, ln, vn = utils.FindKeyNodeFull(label, root.Content) + if vn != nil { + if h, _, _ := utils.IsNodeRefValue(vn); h { + ref := LocateRefNode(vn, idx) + if ref != nil { + vn = ref + } + } + } + } var items []low.ValueReference[T] if vn != nil && ln != nil { for _, node := range vn.Content { + if rf, _, _ := utils.IsNodeRefValue(node); rf { + ref := LocateRefNode(node, idx) + if ref != nil { + node = ref + } + } var n T = new(N) err := BuildModel(node, n) if err != nil { return []low.ValueReference[T]{}, ln, vn, err } - berr := n.Build(node) + berr := n.Build(node, idx) if berr != nil { return nil, ln, vn, berr } @@ -94,7 +173,7 @@ func ExtractArray[T low.Buildable[N], N any](label string, root *yaml.Node) ([]l return items, ln, vn, nil } -func ExtractMapFlatNoLookup[PT low.Buildable[N], N any](root *yaml.Node) (map[low.KeyReference[string]]low.ValueReference[PT], error) { +func ExtractMapFlatNoLookup[PT low.Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (map[low.KeyReference[string]]low.ValueReference[PT], error) { valueMap := make(map[low.KeyReference[string]]low.ValueReference[PT]) if utils.IsNodeMap(root) { var currentKey *yaml.Node @@ -108,7 +187,7 @@ func ExtractMapFlatNoLookup[PT low.Buildable[N], N any](root *yaml.Node) (map[lo if err != nil { return nil, err } - berr := n.Build(node) + berr := n.Build(node, idx) if berr != nil { return nil, berr } @@ -124,8 +203,26 @@ func ExtractMapFlatNoLookup[PT low.Buildable[N], N any](root *yaml.Node) (map[lo return valueMap, nil } -func ExtractMapFlat[PT low.Buildable[N], N any](label string, root *yaml.Node) (map[low.KeyReference[string]]low.ValueReference[PT], *yaml.Node, *yaml.Node, error) { - _, labelNode, valueNode := utils.FindKeyNodeFull(label, root.Content) +func ExtractMapFlat[PT low.Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[low.KeyReference[string]]low.ValueReference[PT], *yaml.Node, *yaml.Node, error) { + var labelNode, valueNode *yaml.Node + if rf, rl, _ := utils.IsNodeRefValue(root); rf { + // locate reference in index. + ref := LocateRefNode(root, idx) + if ref != nil { + valueNode = ref + labelNode = rl + } + } else { + _, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content) + if valueNode != nil { + if h, _, _ := utils.IsNodeRefValue(valueNode); h { + ref := LocateRefNode(valueNode, idx) + if ref != nil { + valueNode = ref + } + } + } + } if valueNode != nil { var currentLabelNode *yaml.Node valueMap := make(map[low.KeyReference[string]]low.ValueReference[PT]) @@ -134,6 +231,15 @@ func ExtractMapFlat[PT low.Buildable[N], N any](label string, root *yaml.Node) ( currentLabelNode = en continue } + + // check our valueNode isn't a reference still. + if h, _, _ := utils.IsNodeRefValue(en); h { + ref := LocateRefNode(en, idx) + if ref != nil { + en = ref + } + } + if strings.HasPrefix(strings.ToLower(currentLabelNode.Value), "x-") { continue // yo, don't pay any attention to extensions, not here anyway. } @@ -142,7 +248,7 @@ func ExtractMapFlat[PT low.Buildable[N], N any](label string, root *yaml.Node) ( if err != nil { return nil, labelNode, valueNode, err } - berr := n.Build(en) + berr := n.Build(en, idx) if berr != nil { return nil, labelNode, valueNode, berr } @@ -159,8 +265,19 @@ func ExtractMapFlat[PT low.Buildable[N], N any](label string, root *yaml.Node) ( return nil, labelNode, valueNode, nil } -func ExtractMap[PT low.Buildable[N], N any](label string, root *yaml.Node) (map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[PT], error) { - _, labelNode, valueNode := utils.FindKeyNodeFull(label, root.Content) +func ExtractMap[PT low.Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[low.KeyReference[string]]map[low.KeyReference[string]]low.ValueReference[PT], error) { + var labelNode, valueNode *yaml.Node + if rf, rl, _ := utils.IsNodeRefValue(root); rf { + // locate reference in index. + ref := LocateRefNode(root, idx) + if ref != nil { + valueNode = ref + labelNode = rl + } + } else { + _, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content) + } + if valueNode != nil { var currentLabelNode *yaml.Node valueMap := make(map[low.KeyReference[string]]low.ValueReference[PT]) @@ -177,7 +294,7 @@ func ExtractMap[PT low.Buildable[N], N any](label string, root *yaml.Node) (map[ if err != nil { return nil, err } - berr := n.Build(en) + berr := n.Build(en, idx) if berr != nil { return nil, berr } @@ -199,16 +316,13 @@ func ExtractMap[PT low.Buildable[N], N any](label string, root *yaml.Node) (map[ return nil, nil } -func ExtractExtensions(root *yaml.Node) (map[low.KeyReference[string]]low.ValueReference[any], error) { +func ExtractExtensions(root *yaml.Node) map[low.KeyReference[string]]low.ValueReference[any] { extensions := utils.FindExtensionNodes(root.Content) extensionMap := make(map[low.KeyReference[string]]low.ValueReference[any]) for _, ext := range extensions { if utils.IsNodeMap(ext.Value) { var v interface{} - err := ext.Value.Decode(&v) - if err != nil { - return nil, err - } + _ = ext.Value.Decode(&v) extensionMap[low.KeyReference[string]{ Value: ext.Key.Value, KeyNode: ext.Key, @@ -243,15 +357,12 @@ func ExtractExtensions(root *yaml.Node) (map[low.KeyReference[string]]low.ValueR } if utils.IsNodeArray(ext.Value) { var v []interface{} - err := ext.Value.Decode(&v) - if err != nil { - return nil, err - } + _ = ext.Value.Decode(&v) extensionMap[low.KeyReference[string]{ Value: ext.Key.Value, KeyNode: ext.Key, }] = low.ValueReference[any]{Value: v, ValueNode: ext.Value} } } - return extensionMap, nil + return extensionMap } diff --git a/datamodel/low/3.0/header.go b/datamodel/low/3.0/header.go index 0324cac..95a9954 100644 --- a/datamodel/low/3.0/header.go +++ b/datamodel/low/3.0/header.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" ) @@ -24,16 +25,11 @@ type Header struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } -func (h *Header) Build(root *yaml.Node) error { - // extract extensions - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - h.Extensions = extensionMap +func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error { + h.Extensions = ExtractExtensions(root) // handle examples if set. - exps, eErr := ExtractMap[*Example](ExamplesLabel, root) + exps, eErr := ExtractMap[*Example](ExamplesLabel, root, idx) if eErr != nil { return eErr } @@ -42,7 +38,7 @@ func (h *Header) Build(root *yaml.Node) error { } // handle schema - sch, sErr := ExtractSchema(root) + sch, sErr := ExtractSchema(root, idx) if sErr != nil { return nil } @@ -51,7 +47,7 @@ func (h *Header) Build(root *yaml.Node) error { } // handle content, if set. - con, cErr := ExtractMap[*MediaType](ContentLabel, root) + con, cErr := ExtractMap[*MediaType](ContentLabel, root, idx) if cErr != nil { return cErr } diff --git a/datamodel/low/3.0/link.go b/datamodel/low/3.0/link.go index 362b9e2..6903018 100644 --- a/datamodel/low/3.0/link.go +++ b/datamodel/low/3.0/link.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -20,12 +21,8 @@ func (l *Link) FindParameter(pName string) *low.ValueReference[string] { return FindItemInMap[string](pName, l.Parameters.Value) } -func (l *Link) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - l.Extensions = extensionMap +func (l *Link) Build(root *yaml.Node, idx *index.SpecIndex) error { + l.Extensions = ExtractExtensions(root) // extract parameters _, pl, pv := utils.FindKeyNodeFull(ParametersLabel, root.Content) diff --git a/datamodel/low/3.0/media_type.go b/datamodel/low/3.0/media_type.go index b8b07b0..cd4b8df 100644 --- a/datamodel/low/3.0/media_type.go +++ b/datamodel/low/3.0/media_type.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -26,14 +27,8 @@ func (mt *MediaType) GetAllExamples() map[low.KeyReference[string]]low.ValueRefe return mt.Examples.Value } -func (mt *MediaType) Build(root *yaml.Node) error { - - // extract extensions - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - mt.Extensions = extensionMap +func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error { + mt.Extensions = ExtractExtensions(root) // handle example if set. _, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content) @@ -42,7 +37,7 @@ func (mt *MediaType) Build(root *yaml.Node) error { } // handle schema - sch, sErr := ExtractSchema(root) + sch, sErr := ExtractSchema(root, idx) if sErr != nil { return nil } @@ -51,7 +46,7 @@ func (mt *MediaType) Build(root *yaml.Node) error { } // handle examples if set. - exps, expsL, expsN, eErr := ExtractMapFlat[*Example](ExamplesLabel, root) + exps, expsL, expsN, eErr := ExtractMapFlat[*Example](ExamplesLabel, root, idx) if eErr != nil { return eErr } @@ -64,9 +59,9 @@ func (mt *MediaType) Build(root *yaml.Node) error { } // handle encoding - encs, encsL, encsN, encErr := ExtractMapFlat[*Encoding](EncodingLabel, root) + encs, encsL, encsN, encErr := ExtractMapFlat[*Encoding](EncodingLabel, root, idx) if encErr != nil { - return err + return encErr } if encs != nil { mt.Encoding = low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*Encoding]]{ diff --git a/datamodel/low/3.0/oauth_flows.go b/datamodel/low/3.0/oauth_flows.go index 801b64a..80c490f 100644 --- a/datamodel/low/3.0/oauth_flows.go +++ b/datamodel/low/3.0/oauth_flows.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -22,32 +23,28 @@ type OAuthFlows struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } -func (o *OAuthFlows) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - o.Extensions = extensionMap +func (o *OAuthFlows) Build(root *yaml.Node, idx *index.SpecIndex) error { + o.Extensions = ExtractExtensions(root) - v, vErr := ExtractObject[*OAuthFlow](ImplicitLabel, root) + v, vErr := ExtractObject[*OAuthFlow](ImplicitLabel, root, idx) if vErr != nil { return vErr } o.Implicit = v - v, vErr = ExtractObject[*OAuthFlow](PasswordLabel, root) + v, vErr = ExtractObject[*OAuthFlow](PasswordLabel, root, idx) if vErr != nil { return vErr } o.Password = v - v, vErr = ExtractObject[*OAuthFlow](ClientCredentialsLabel, root) + v, vErr = ExtractObject[*OAuthFlow](ClientCredentialsLabel, root, idx) if vErr != nil { return vErr } o.ClientCredentials = v - v, vErr = ExtractObject[*OAuthFlow](AuthorizationCodeLabel, root) + v, vErr = ExtractObject[*OAuthFlow](AuthorizationCodeLabel, root, idx) if vErr != nil { return vErr } @@ -68,12 +65,8 @@ func (o *OAuthFlow) FindScope(scope string) *low.ValueReference[string] { return FindItemInMap[string](scope, o.Scopes.Value) } -func (o *OAuthFlow) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - o.Extensions = extensionMap +func (o *OAuthFlow) Build(root *yaml.Node, idx *index.SpecIndex) error { + o.Extensions = ExtractExtensions(root) var currSec *yaml.Node diff --git a/datamodel/low/3.0/operation.go b/datamodel/low/3.0/operation.go index 0c58095..bc62f7e 100644 --- a/datamodel/low/3.0/operation.go +++ b/datamodel/low/3.0/operation.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" ) @@ -29,22 +30,18 @@ type Operation struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } -func (o *Operation) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - o.Extensions = extensionMap +func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error { + o.Extensions = ExtractExtensions(root) // extract externalDocs - extDocs, dErr := ExtractObject[*ExternalDoc](ExternalDocsLabel, root) + extDocs, dErr := ExtractObject[*ExternalDoc](ExternalDocsLabel, root, idx) if dErr != nil { return dErr } o.ExternalDocs = extDocs // extract parameters - params, ln, vn, pErr := ExtractArray[*Parameter](ParametersLabel, root) + params, ln, vn, pErr := ExtractArray[*Parameter](ParametersLabel, root, idx) if pErr != nil { return pErr } @@ -57,21 +54,21 @@ func (o *Operation) Build(root *yaml.Node) error { } // extract request body - rBody, rErr := ExtractObject[*RequestBody](RequestBodyLabel, root) + rBody, rErr := ExtractObject[*RequestBody](RequestBodyLabel, root, idx) if rErr != nil { return rErr } o.RequestBody = rBody // extract responses - respBody, respErr := ExtractObject[*Responses](ResponsesLabel, root) + respBody, respErr := ExtractObject[*Responses](ResponsesLabel, root, idx) if respErr != nil { return rErr } o.Responses = respBody // extract callbacks - callbacks, cbL, cbN, cbErr := ExtractMapFlat[*Callback](CallbacksLabel, root) + callbacks, cbL, cbN, cbErr := ExtractMapFlat[*Callback](CallbacksLabel, root, idx) if cbErr != nil { return cbErr } @@ -84,14 +81,14 @@ func (o *Operation) Build(root *yaml.Node) error { } // extract security - sec, sErr := ExtractObject[*SecurityRequirement](SecurityLabel, root) + sec, sErr := ExtractObject[*SecurityRequirement](SecurityLabel, root, idx) if sErr != nil { return sErr } o.Security = sec // extract servers - servers, sl, sn, serErr := ExtractArray[*Server](ServersLabel, root) + servers, sl, sn, serErr := ExtractArray[*Server](ServersLabel, root, idx) if serErr != nil { return serErr } diff --git a/datamodel/low/3.0/parameter.go b/datamodel/low/3.0/parameter.go index 72edebe..7a30fd9 100644 --- a/datamodel/low/3.0/parameter.go +++ b/datamodel/low/3.0/parameter.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -36,14 +37,8 @@ func (p *Parameter) FindExample(eType string) *low.ValueReference[*Example] { return FindItemInMap[*Example](eType, p.Examples.Value) } -func (p *Parameter) Build(root *yaml.Node) error { - - // extract extensions - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - p.Extensions = extensionMap +func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error { + p.Extensions = ExtractExtensions(root) // handle example if set. _, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content) @@ -52,14 +47,16 @@ func (p *Parameter) Build(root *yaml.Node) error { } // handle schema - sch, sErr := ExtractSchema(root) + sch, sErr := ExtractSchema(root, idx) if sErr != nil { return sErr } - p.Schema = *sch + if sch != nil { + p.Schema = *sch + } // handle examples if set. - exps, expsL, expsN, eErr := ExtractMapFlat[*Example](ExamplesLabel, root) + exps, expsL, expsN, eErr := ExtractMapFlat[*Example](ExamplesLabel, root, idx) if eErr != nil { return eErr } @@ -72,7 +69,7 @@ func (p *Parameter) Build(root *yaml.Node) error { } // handle content, if set. - con, cL, cN, cErr := ExtractMapFlat[*MediaType](ContentLabel, root) + con, cL, cN, cErr := ExtractMapFlat[*MediaType](ContentLabel, root, idx) if cErr != nil { return cErr } diff --git a/datamodel/low/3.0/path.go b/datamodel/low/3.0/path.go index ffb3f91..f71dee7 100644 --- a/datamodel/low/3.0/path.go +++ b/datamodel/low/3.0/path.go @@ -2,6 +2,8 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" + "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" "strings" "sync" @@ -33,14 +35,8 @@ func (p *Paths) FindPath(path string) *low.ValueReference[*PathItem] { return nil } -func (p *Paths) Build(root *yaml.Node) error { - - // extract extensions - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - p.Extensions = extensionMap +func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error { + p.Extensions = ExtractExtensions(root) skip := false var currentNode *yaml.Node @@ -60,11 +56,11 @@ func (p *Paths) Build(root *yaml.Node) error { continue } var path = PathItem{} - err = BuildModel(pathNode, &path) + err := BuildModel(pathNode, &path) if err != nil { } - err = path.Build(pathNode) + err = path.Build(pathNode, idx) if err != nil { return err } @@ -100,12 +96,8 @@ type PathItem struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } -func (p *PathItem) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - p.Extensions = extensionMap +func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error { + p.Extensions = ExtractExtensions(root) skip := false var currentNode *yaml.Node @@ -114,6 +106,15 @@ func (p *PathItem) Build(root *yaml.Node) error { var ops []low.NodeReference[*Operation] + if ok, _, _ := utils.IsNodeRefValue(root); ok { + r := LocateRefNode(root, idx) + if r != nil { + root = r + } else { + return nil + } + } + for i, pathNode := range root.Content { if strings.HasPrefix(strings.ToLower(pathNode.Value), "x-") { skip = true @@ -161,18 +162,20 @@ func (p *PathItem) Build(root *yaml.Node) error { } } - wg.Wait() + if len(ops) > 0 { + //wg.Wait() + } - // all operations have been superficially built, - // now we need to build out the operation, we will do this asynchronously for speed. + //all operations have been superficially built, + //now we need to build out the operation, we will do this asynchronously for speed. opBuildChan := make(chan bool) opErrorChan := make(chan error) var buildOpFunc = func(op low.NodeReference[*Operation], ch chan<- bool, errCh chan<- error) { - // build out the operation. - er := op.Value.Build(op.ValueNode) - if err != nil { + //build out the operation. + er := op.Value.Build(op.ValueNode, idx) + if er != nil { errCh <- er } ch <- true diff --git a/datamodel/low/3.0/request_body.go b/datamodel/low/3.0/request_body.go index 2d5a131..e856909 100644 --- a/datamodel/low/3.0/request_body.go +++ b/datamodel/low/3.0/request_body.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" ) @@ -16,18 +17,11 @@ func (rb *RequestBody) FindContent(cType string) *low.ValueReference[*MediaType] return FindItemInMap[*MediaType](cType, rb.Content.Value) } -func (rb *RequestBody) Build(root *yaml.Node) error { - // extract extensions - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - if extensionMap != nil { - rb.Extensions = extensionMap - } +func (rb *RequestBody) Build(root *yaml.Node, idx *index.SpecIndex) error { + rb.Extensions = ExtractExtensions(root) // handle content, if set. - con, cL, cN, cErr := ExtractMapFlat[*MediaType](ContentLabel, root) + con, cL, cN, cErr := ExtractMapFlat[*MediaType](ContentLabel, root, idx) if cErr != nil { return cErr } diff --git a/datamodel/low/3.0/response.go b/datamodel/low/3.0/response.go index e5f4185..725059f 100644 --- a/datamodel/low/3.0/response.go +++ b/datamodel/low/3.0/response.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -15,9 +16,9 @@ type Responses struct { Default low.NodeReference[*Response] } -func (r *Responses) Build(root *yaml.Node) error { +func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error { if utils.IsNodeMap(root) { - codes, err := ExtractMapFlatNoLookup[*Response](root) + codes, err := ExtractMapFlatNoLookup[*Response](root, idx) if err != nil { return err } @@ -52,15 +53,11 @@ func (r *Response) FindLink(hType string) *low.ValueReference[*Link] { return FindItemInMap[*Link](hType, r.Links.Value) } -func (r *Response) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - r.Extensions = extensionMap +func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error { + r.Extensions = ExtractExtensions(root) // extract headers - headers, lN, kN, err := ExtractMapFlat[*Header](HeadersLabel, root) + headers, lN, kN, err := ExtractMapFlat[*Header](HeadersLabel, root, idx) if err != nil { return err } @@ -73,7 +70,7 @@ func (r *Response) Build(root *yaml.Node) error { } // handle content, if set. - con, clN, cN, cErr := ExtractMapFlat[*MediaType](ContentLabel, root) + con, clN, cN, cErr := ExtractMapFlat[*MediaType](ContentLabel, root, idx) if cErr != nil { return cErr } @@ -86,7 +83,7 @@ func (r *Response) Build(root *yaml.Node) error { } // handle links if set - links, linkLabel, linkValue, lErr := ExtractMapFlat[*Link](LinksLabel, root) + links, linkLabel, linkValue, lErr := ExtractMapFlat[*Link](LinksLabel, root, idx) if lErr != nil { return lErr } diff --git a/datamodel/low/3.0/schema.go b/datamodel/low/3.0/schema.go index 05b038a..2ceeed5 100644 --- a/datamodel/low/3.0/schema.go +++ b/datamodel/low/3.0/schema.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" "strconv" @@ -61,20 +62,17 @@ func (s *Schema) FindProperty(name string) *low.ValueReference[*Schema] { return FindItemInMap[*Schema](name, s.Properties.Value) } -func (s *Schema) Build(root *yaml.Node) error { - return s.BuildLevel(root, 0) +func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { + return s.BuildLevel(root, idx, 0) } -func (s *Schema) BuildLevel(root *yaml.Node, level int) error { +func (s *Schema) BuildLevel(root *yaml.Node, idx *index.SpecIndex, level int) error { level++ - if level > 50 { + if level > 10 { return nil // we're done, son! too fricken deep. } - err := s.extractExtensions(root) - if err != nil { - return err - } + s.extractExtensions(root) // handle example if set. _, expLabel, expNode := utils.FindKeyNodeFull(ExampleLabel, root.Content) @@ -85,7 +83,7 @@ func (s *Schema) BuildLevel(root *yaml.Node, level int) error { _, addPLabel, addPNode := utils.FindKeyNodeFull(AdditionalPropertiesLabel, root.Content) if addPNode != nil { if utils.IsNodeMap(addPNode) { - schema, serr := ExtractObjectRaw[*Schema](addPNode) + schema, serr := ExtractObjectRaw[*Schema](addPNode, idx) if serr != nil { return serr } @@ -102,7 +100,7 @@ func (s *Schema) BuildLevel(root *yaml.Node, level int) error { _, discLabel, discNode := utils.FindKeyNodeFull(DiscriminatorLabel, root.Content) if discNode != nil { var discriminator Discriminator - err = BuildModel(discNode, &discriminator) + err := BuildModel(discNode, &discriminator) if err != nil { return err } @@ -113,11 +111,11 @@ func (s *Schema) BuildLevel(root *yaml.Node, level int) error { _, extDocLabel, extDocNode := utils.FindKeyNodeFull(ExternalDocsLabel, root.Content) if extDocNode != nil { var exDoc ExternalDoc - err = BuildModel(extDocNode, &exDoc) + err := BuildModel(extDocNode, &exDoc) if err != nil { return err } - err = exDoc.Build(extDocNode) + err = exDoc.Build(extDocNode, idx) if err != nil { return err } @@ -128,7 +126,7 @@ func (s *Schema) BuildLevel(root *yaml.Node, level int) error { _, xmlLabel, xmlNode := utils.FindKeyNodeFull(XMLLabel, root.Content) if xmlNode != nil { var xml XML - err = BuildModel(xmlNode, &xml) + err := BuildModel(xmlNode, &xml) if err != nil { return err } @@ -150,12 +148,21 @@ func (s *Schema) BuildLevel(root *yaml.Node, level int) error { currentProp = prop continue } + + // check our prop isn't reference + if h, _, _ := utils.IsNodeRefValue(prop); h { + ref := LocateRefNode(prop, idx) + if ref != nil { + prop = ref + } + } + var property Schema - err = BuildModel(prop, &property) + err := BuildModel(prop, &property) if err != nil { return err } - err = property.BuildLevel(prop, level) + err = property.BuildLevel(prop, idx, level) if err != nil { return err } @@ -179,11 +186,11 @@ func (s *Schema) BuildLevel(root *yaml.Node, level int) error { var allOf, anyOf, oneOf, not, items []low.NodeReference[*Schema] // make this async at some point to speed things up. - allOfLabel, allOfValue := buildSchema(&allOf, AllOfLabel, root, level, &errors) - anyOfLabel, anyOfValue := buildSchema(&anyOf, AnyOfLabel, root, level, &errors) - oneOfLabel, oneOfValue := buildSchema(&oneOf, OneOfLabel, root, level, &errors) - notLabel, notValue := buildSchema(¬, NotLabel, root, level, &errors) - itemsLabel, itemsValue := buildSchema(&items, ItemsLabel, root, level, &errors) + allOfLabel, allOfValue := buildSchema(&allOf, AllOfLabel, root, level, &errors, idx) + anyOfLabel, anyOfValue := buildSchema(&anyOf, AnyOfLabel, root, level, &errors, idx) + oneOfLabel, oneOfValue := buildSchema(&oneOf, OneOfLabel, root, level, &errors, idx) + notLabel, notValue := buildSchema(¬, NotLabel, root, level, &errors, idx) + itemsLabel, itemsValue := buildSchema(&items, ItemsLabel, root, level, &errors, idx) if len(errors) > 0 { // todo fix this @@ -228,16 +235,13 @@ func (s *Schema) BuildLevel(root *yaml.Node, level int) error { return nil } -func (s *Schema) extractExtensions(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - s.Extensions = extensionMap - return err +func (s *Schema) extractExtensions(root *yaml.Node) { + s.Extensions = ExtractExtensions(root) } -func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNode *yaml.Node, level int, errors *[]error) (labelNode *yaml.Node, valueNode *yaml.Node) { +func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNode *yaml.Node, level int, + errors *[]error, idx *index.SpecIndex) (labelNode *yaml.Node, valueNode *yaml.Node) { + _, labelNode, valueNode = utils.FindKeyNodeFull(attribute, rootNode.Content) //wg.Add(1) if valueNode != nil { @@ -248,7 +252,7 @@ func buildSchema(schemas *[]low.NodeReference[*Schema], attribute string, rootNo *errors = append(*errors, err) return nil } - err = schema.BuildLevel(vn, level) + err = schema.BuildLevel(vn, idx, 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 bf817a8..6204ff6 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]) + schErr := sch.Build(rootNode.Content[0], nil) 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 7c5c785..3213afc 100644 --- a/datamodel/low/3.0/security_scheme.go +++ b/datamodel/low/3.0/security_scheme.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -28,14 +29,10 @@ type SecurityRequirement struct { Value []low.ValueReference[map[low.KeyReference[string]][]low.ValueReference[string]] } -func (ss *SecurityScheme) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - ss.Extensions = extensionMap +func (ss *SecurityScheme) Build(root *yaml.Node, idx *index.SpecIndex) error { + ss.Extensions = ExtractExtensions(root) - oa, oaErr := ExtractObject[*OAuthFlows](OAuthFlowsLabel, root) + oa, oaErr := ExtractObject[*OAuthFlows](OAuthFlowsLabel, root, idx) if oaErr != nil { return oaErr } @@ -57,7 +54,7 @@ func (sr *SecurityRequirement) FindRequirement(name string) []low.ValueReference return nil } -func (sr *SecurityRequirement) Build(root *yaml.Node) error { +func (sr *SecurityRequirement) Build(root *yaml.Node, idx *index.SpecIndex) error { if utils.IsNodeArray(root) { var requirements []low.ValueReference[map[low.KeyReference[string]][]low.ValueReference[string]] for _, n := range root.Content { diff --git a/datamodel/low/3.0/server.go b/datamodel/low/3.0/server.go index 9a1a7a3..b5cf096 100644 --- a/datamodel/low/3.0/server.go +++ b/datamodel/low/3.0/server.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" "gopkg.in/yaml.v3" ) @@ -17,7 +18,7 @@ type Server struct { Variables low.NodeReference[map[string]low.NodeReference[*ServerVariable]] } -func (s *Server) Build(root *yaml.Node) error { +func (s *Server) Build(root *yaml.Node, idx *index.SpecIndex) error { kn, vars := utils.FindKeyNode(VariablesLabel, root.Content) if vars == nil { return nil diff --git a/datamodel/low/3.0/tag.go b/datamodel/low/3.0/tag.go index 5e58899..d63a254 100644 --- a/datamodel/low/3.0/tag.go +++ b/datamodel/low/3.0/tag.go @@ -2,6 +2,7 @@ package v3 import ( "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/index" "gopkg.in/yaml.v3" ) @@ -17,16 +18,11 @@ type Tag struct { Extensions map[low.KeyReference[string]]low.ValueReference[any] } -func (t *Tag) Build(root *yaml.Node) error { - // extract extensions - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - t.Extensions = extensionMap +func (t *Tag) Build(root *yaml.Node, idx *index.SpecIndex) error { + t.Extensions = ExtractExtensions(root) // extract externalDocs - extDocs, dErr := ExtractObject[*ExternalDoc](ExternalDocsLabel, root) + extDocs, dErr := ExtractObject[*ExternalDoc](ExternalDocsLabel, root, idx) if dErr != nil { return dErr } diff --git a/datamodel/low/3.0/xml.go b/datamodel/low/3.0/xml.go index dfe3512..051515e 100644 --- a/datamodel/low/3.0/xml.go +++ b/datamodel/low/3.0/xml.go @@ -15,10 +15,6 @@ type XML struct { } func (x *XML) Build(root *yaml.Node) error { - extensionMap, err := ExtractExtensions(root) - if err != nil { - return err - } - x.Extensions = extensionMap + x.Extensions = ExtractExtensions(root) return nil } diff --git a/datamodel/low/reference.go b/datamodel/low/reference.go index 39e7c2f..0a53ffb 100644 --- a/datamodel/low/reference.go +++ b/datamodel/low/reference.go @@ -1,13 +1,16 @@ package low -import "gopkg.in/yaml.v3" +import ( + "github.com/pb33f/libopenapi/index" + "gopkg.in/yaml.v3" +) type HasNode interface { GetNode() *yaml.Node } type Buildable[T any] interface { - Build(node *yaml.Node) error + Build(node *yaml.Node, idx *index.SpecIndex) error *T } diff --git a/index/spec_index.go b/index/spec_index.go index 0eb73af..6ddfe08 100644 --- a/index/spec_index.go +++ b/index/spec_index.go @@ -16,9 +16,9 @@ import ( ) const ( - localResolve int = 0 - httpResolve int = 1 - fileResolve int = 2 + LocalResolve int = 0 + HttpResolve int = 1 + FileResolve int = 2 ) // Reference is a wrapper around *yaml.Node results to make things more manageable when performing @@ -72,7 +72,8 @@ type SpecIndex struct { responsesRefs map[string]*Reference // top level responses headersRefs map[string]*Reference // top level responses examplesRefs map[string]*Reference // top level examples - linksRefs map[string]map[string][]*Reference // all links + callbacksRefs map[string]map[string][]*Reference // all links + linksRefs map[string]map[string][]*Reference // all callbacks operationTagsRefs map[string]map[string][]*Reference // tags found in operations operationDescriptionRefs map[string]map[string]*Reference // descriptions in operations. operationSummaryRefs map[string]map[string]*Reference // summaries in operations @@ -99,6 +100,7 @@ type SpecIndex struct { globalHeadersCount int // component headers globalExamplesCount int // component examples globalLinksCount int // component links + globalCallbacksCount int // component callbacks globalCallbacks int // component callbacks. pathCount int // number of paths operationCount int // number of operations @@ -207,6 +209,7 @@ func NewSpecIndex(rootNode *yaml.Node) *SpecIndex { index.responsesRefs = make(map[string]*Reference) index.headersRefs = make(map[string]*Reference) index.examplesRefs = make(map[string]*Reference) + index.callbacksRefs = make(map[string]map[string][]*Reference) index.linksRefs = make(map[string]map[string][]*Reference) index.callbackRefs = make(map[string]*Reference) index.externalSpecIndex = make(map[string]*SpecIndex) @@ -256,6 +259,7 @@ func NewSpecIndex(rootNode *yaml.Node) *SpecIndex { index.GetInlineUniqueParamCount, index.GetOperationTagsCount, index.GetGlobalLinksCount, + index.GetGlobalCallbacksCount, } wg.Add(len(countFuncs)) @@ -872,13 +876,59 @@ func (index *SpecIndex) GetTotalTagsCount() int { return index.totalTagsCount } -// GetGlobalLinksCount for each response of each operation method, multiple links can be defined +// GetGlobalCallbacksCount for each response of each operation method, multiple links can be defined +func (index *SpecIndex) GetGlobalCallbacksCount() int { + if index.root == nil { + return -1 + } + + if index.globalCallbacksCount > 0 { + return index.globalCallbacksCount + } + + index.pathRefsLock.Lock() + for path, p := range index.pathRefs { + for _, m := range p { + + // look through method for callbacks + callbacks, _ := yamlpath.NewPath("$..callbacks") + res, _ := callbacks.Find(m.Node) + + if len(res) > 0 { + + for _, callback := range res[0].Content { + if utils.IsNodeMap(callback) { + + ref := &Reference{ + Definition: m.Name, + Name: m.Name, + Node: callback, + } + + if index.callbacksRefs[path] == nil { + index.callbacksRefs[path] = make(map[string][]*Reference) + } + if len(index.callbacksRefs[path][m.Name]) > 0 { + index.callbacksRefs[path][m.Name] = append(index.callbacksRefs[path][m.Name], ref) + } + index.callbacksRefs[path][m.Name] = []*Reference{ref} + index.globalCallbacksCount++ + } + } + } + } + } + index.pathRefsLock.Unlock() + return index.globalCallbacksCount +} + +// GetGlobalLinksCount for each response of each operation method, multiple callbacks can be defined func (index *SpecIndex) GetGlobalLinksCount() int { if index.root == nil { return -1 } - if index.globalLinksCount > 0 { + if index.globalCallbacksCount > 0 { return index.globalLinksCount } @@ -900,7 +950,6 @@ func (index *SpecIndex) GetGlobalLinksCount() int { Name: m.Name, Node: link, } - if index.linksRefs[path] == nil { index.linksRefs[path] = make(map[string][]*Reference) } @@ -1421,17 +1470,17 @@ func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Re return index.lookupFileReference(id) } - switch determineReferenceResolveType(componentId) { - case localResolve: // ideally, every single ref in every single spec is local. however, this is not the case. - return index.findComponentInRoot(componentId) + switch DetermineReferenceResolveType(componentId) { + case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case. + return index.FindComponentInRoot(componentId) - case httpResolve: + case HttpResolve: uri := strings.Split(componentId, "#") if len(uri) == 2 { return index.performExternalLookup(uri, componentId, remoteLookup, parent) } - case fileResolve: + case FileResolve: uri := strings.Split(componentId, "#") if len(uri) == 2 { return index.performExternalLookup(uri, componentId, fileLookup, parent) @@ -1450,23 +1499,23 @@ func (index *SpecIndex) GetAllSummariesCount() int { return len(index.allSummaries) } -/* private */ - -func determineReferenceResolveType(ref string) int { +func DetermineReferenceResolveType(ref string) int { if ref != "" && ref[0] == '#' { - return localResolve + return LocalResolve } if ref != "" && len(ref) >= 5 && (ref[:5] == "https" || ref[:5] == "http:") { - return httpResolve + return HttpResolve } if strings.Contains(ref, ".json") || strings.Contains(ref, ".yaml") || strings.Contains(ref, ".yml") { - return fileResolve + return FileResolve } return -1 } +/* private */ + func (index *SpecIndex) extractDefinitionsAndSchemas(schemasNode *yaml.Node, pathPrefix string) { var name string for i, schema := range schemasNode.Content { @@ -1650,7 +1699,7 @@ func (index *SpecIndex) performExternalLookup(uri []string, componentId string, } else { - foundRef := externalSpecIndex.findComponentInRoot(uri[1]) + foundRef := externalSpecIndex.FindComponentInRoot(uri[1]) if foundRef != nil { foundNode = foundRef.Node } @@ -1670,7 +1719,7 @@ func (index *SpecIndex) performExternalLookup(uri []string, componentId string, return nil } -func (index *SpecIndex) findComponentInRoot(componentId string) *Reference { +func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference { name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) diff --git a/index/spec_index_test.go b/index/spec_index_test.go index 230998e..789dc22 100644 --- a/index/spec_index_test.go +++ b/index/spec_index_test.go @@ -348,7 +348,7 @@ func TestSpecIndex_TestEmptyBrokenReferences(t *testing.T) { assert.Equal(t, 2, index.GetGlobalTagsCount()) assert.Equal(t, 3, index.GetTotalTagsCount()) assert.Equal(t, 2, index.GetOperationTagsCount()) - assert.Equal(t, 2, index.GetGlobalLinksCount()) + assert.Equal(t, 4, index.GetGlobalLinksCount()) assert.Equal(t, 0, index.GetComponentParameterCount()) assert.Equal(t, 2, index.GetOperationsParameterCount()) assert.Equal(t, 1, index.GetInlineDuplicateParamCount()) diff --git a/openapi/create_document.go b/openapi/create_document.go index 834bc71..9cf8ee6 100644 --- a/openapi/create_document.go +++ b/openapi/create_document.go @@ -5,7 +5,6 @@ import ( "github.com/pb33f/libopenapi/datamodel/low" v3 "github.com/pb33f/libopenapi/datamodel/low/3.0" "github.com/pb33f/libopenapi/index" - "github.com/pb33f/libopenapi/resolver" "github.com/pb33f/libopenapi/utils" "sync" ) @@ -15,26 +14,27 @@ func CreateDocument(info *datamodel.SpecInfo) (*v3.Document, error) { doc := v3.Document{Version: low.NodeReference[string]{Value: info.Version, ValueNode: info.RootNode}} // build an index - rsolvr := resolver.NewResolver(index.NewSpecIndex(info.RootNode)) + idx := index.NewSpecIndex(info.RootNode) + //rsolvr := resolver.NewResolver() // todo handle errors - rsolvr.Resolve() + //rsolvr.Resolve() var wg sync.WaitGroup var errors []error - var runExtraction = func(info *datamodel.SpecInfo, doc *v3.Document, - runFunc func(i *datamodel.SpecInfo, d *v3.Document) error, + var runExtraction = func(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex, + runFunc func(i *datamodel.SpecInfo, d *v3.Document, idx *index.SpecIndex) error, ers *[]error, wg *sync.WaitGroup) { - if er := runFunc(info, doc); er != nil { + if er := runFunc(info, doc, idx); er != nil { *ers = append(*ers, er) } wg.Done() } - extractionFuncs := []func(i *datamodel.SpecInfo, d *v3.Document) error{ + extractionFuncs := []func(i *datamodel.SpecInfo, d *v3.Document, idx *index.SpecIndex) error{ extractInfo, extractServers, extractTags, @@ -45,7 +45,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*v3.Document, error) { } wg.Add(len(extractionFuncs)) for _, f := range extractionFuncs { - go runExtraction(info, &doc, f, &errors, &wg) + go runExtraction(info, &doc, idx, f, &errors, &wg) } wg.Wait() @@ -56,7 +56,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*v3.Document, error) { return &doc, nil } -func extractInfo(info *datamodel.SpecInfo, doc *v3.Document) error { +func extractInfo(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error { _, ln, vn := utils.FindKeyNodeFull(v3.InfoLabel, info.RootNode.Content) if vn != nil { ir := v3.Info{} @@ -71,8 +71,8 @@ func extractInfo(info *datamodel.SpecInfo, doc *v3.Document) error { return nil } -func extractSecurity(info *datamodel.SpecInfo, doc *v3.Document) error { - sec, sErr := v3.ExtractObject[*v3.SecurityRequirement](v3.SecurityLabel, info.RootNode) +func extractSecurity(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error { + sec, sErr := v3.ExtractObject[*v3.SecurityRequirement](v3.SecurityLabel, info.RootNode, idx) if sErr != nil { return sErr } @@ -80,8 +80,8 @@ func extractSecurity(info *datamodel.SpecInfo, doc *v3.Document) error { return nil } -func extractExternalDocs(info *datamodel.SpecInfo, doc *v3.Document) error { - extDocs, dErr := v3.ExtractObject[*v3.ExternalDoc](v3.ExternalDocsLabel, info.RootNode) +func extractExternalDocs(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error { + extDocs, dErr := v3.ExtractObject[*v3.ExternalDoc](v3.ExternalDocsLabel, info.RootNode, idx) if dErr != nil { return dErr } @@ -89,7 +89,7 @@ func extractExternalDocs(info *datamodel.SpecInfo, doc *v3.Document) error { return nil } -func extractComponents(info *datamodel.SpecInfo, doc *v3.Document) error { +func extractComponents(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error { _, ln, vn := utils.FindKeyNodeFull(v3.ComponentsLabel, info.RootNode.Content) if vn != nil { ir := v3.Components{} @@ -97,14 +97,14 @@ func extractComponents(info *datamodel.SpecInfo, doc *v3.Document) error { if err != nil { return err } - err = ir.Build(vn) + err = ir.Build(vn, idx) 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 { +func extractServers(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error { _, ln, vn := utils.FindKeyNodeFull(v3.ServersLabel, info.RootNode.Content) if vn != nil { if utils.IsNodeArray(vn) { @@ -116,7 +116,7 @@ func extractServers(info *datamodel.SpecInfo, doc *v3.Document) error { if err != nil { return err } - srvr.Build(srvN) + srvr.Build(srvN, idx) servers = append(servers, low.ValueReference[*v3.Server]{ Value: &srvr, ValueNode: srvN, @@ -133,7 +133,7 @@ func extractServers(info *datamodel.SpecInfo, doc *v3.Document) error { return nil } -func extractTags(info *datamodel.SpecInfo, doc *v3.Document) error { +func extractTags(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error { _, ln, vn := utils.FindKeyNodeFull(v3.TagsLabel, info.RootNode.Content) if vn != nil { if utils.IsNodeArray(vn) { @@ -145,7 +145,7 @@ func extractTags(info *datamodel.SpecInfo, doc *v3.Document) error { if err != nil { return err } - tag.Build(tagN) + tag.Build(tagN, idx) tags = append(tags, low.ValueReference[*v3.Tag]{ Value: &tag, ValueNode: tagN, @@ -162,11 +162,11 @@ func extractTags(info *datamodel.SpecInfo, doc *v3.Document) error { return nil } -func extractPaths(info *datamodel.SpecInfo, doc *v3.Document) error { +func extractPaths(info *datamodel.SpecInfo, doc *v3.Document, idx *index.SpecIndex) error { _, ln, vn := utils.FindKeyNodeFull(v3.PathsLabel, info.RootNode.Content) if vn != nil { ir := v3.Paths{} - err := ir.Build(vn) + err := ir.Build(vn, idx) if err != nil { return err } diff --git a/openapi/create_document_test.go b/openapi/create_document_test.go index aa4c671..2b9898c 100644 --- a/openapi/create_document_test.go +++ b/openapi/create_document_test.go @@ -24,6 +24,17 @@ func BenchmarkCreateDocument(b *testing.B) { } } +func BenchmarkCreateDocument_Stripe(b *testing.B) { + data, _ := ioutil.ReadFile("../test_specs/stripe.yaml") + info, _ := datamodel.ExtractSpecInfo(data) + for i := 0; i < b.N; i++ { + _, err := CreateDocument(info) + if err != nil { + panic("this should not error") + } + } +} + func TestCreateDocument(t *testing.T) { assert.Equal(t, "3.0.1", doc.Version.Value) assert.Equal(t, "Burger Shop", doc.Info.Value.Title.Value) @@ -254,8 +265,9 @@ func TestCreateDocument_Components_Schemas(t *testing.T) { assert.NotNil(t, fries.Value) assert.Len(t, fries.Value.Properties.Value, 3) + p := fries.Value.FindProperty("favoriteDrink") assert.Equal(t, "a frosty cold beverage can be coke or sprite", - fries.Value.FindProperty("favoriteDrink").Value.Description.Value) + p.Value.Description.Value) } diff --git a/utils/utils.go b/utils/utils.go index 76e6c5a..156a6fc 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -372,6 +372,17 @@ func IsNodeBoolValue(node *yaml.Node) bool { return node.Tag == "!!bool" } +func IsNodeRefValue(node *yaml.Node) (bool, *yaml.Node, string) { + for i, r := range node.Content { + if i%2 == 0 { + if r.Value == "$ref" { + return true, r, node.Content[i+1].Value + } + } + } + return false, nil, "" +} + // FixContext will clean up a JSONpath string to be correctly traversable. func FixContext(context string) string {