Added NodeMap to all low level objects.

this sync map tracks all the nodes that apply to this object.
This commit is contained in:
quobix
2024-08-04 11:01:23 -04:00
parent 31ed123abd
commit 70f406b6cf
46 changed files with 712 additions and 44 deletions

View File

@@ -150,7 +150,7 @@ components:
assert.ErrorIs(t, unwrap[0], ErrInvalidModel) assert.ErrorIs(t, unwrap[0], ErrInvalidModel)
unwrapNext := utils.UnwrapErrors(unwrap[1]) unwrapNext := utils.UnwrapErrors(unwrap[1])
require.Len(t, unwrapNext, 2) require.Len(t, unwrapNext, 2)
assert.Equal(t, "component 'bork' does not exist in the specification", unwrapNext[0].Error()) assert.Equal(t, "component `bork` does not exist in the specification", unwrapNext[0].Error())
assert.Equal(t, "cannot resolve reference `bork`, it's missing: $bork [5:7]", unwrapNext[1].Error()) assert.Equal(t, "cannot resolve reference `bork`, it's missing: $bork [5:7]", unwrapNext[1].Error())
logEntries := strings.Split(byteBuf.String(), "\n") logEntries := strings.Split(byteBuf.String(), "\n")

View File

@@ -72,6 +72,14 @@ func CreateSchemaProxyRef(ref string) *SchemaProxy {
return &SchemaProxy{refStr: ref, lock: &sync.Mutex{}} return &SchemaProxy{refStr: ref, lock: &sync.Mutex{}}
} }
// GetValueNode returns the value node of the SchemaProxy.
func (sp *SchemaProxy) GetValueNode() *yaml.Node {
if sp.schema != nil {
return sp.schema.ValueNode
}
return nil
}
// Schema will create a new Schema instance using NewSchema from the low-level SchemaProxy backing this high-level one. // Schema will create a new Schema instance using NewSchema from the low-level SchemaProxy backing this high-level one.
// If there is a problem building the Schema, then this method will return nil. Use GetBuildError to gain access // If there is a problem building the Schema, then this method will return nil. Use GetBuildError to gain access
// to that building error. // to that building error.

View File

@@ -443,7 +443,7 @@ func (n *NodeBuilder) AddYAMLNode(parent *yaml.Node, entry *nodes.NodeEntry) *ya
lr := lut.(low.IsReferenced) lr := lut.(low.IsReferenced)
ut := reflect.ValueOf(lr) ut := reflect.ValueOf(lr)
if !ut.IsNil() { if !ut.IsNil() {
if lut.(low.IsReferenced).IsReference() { if lr != nil && lr.IsReference() {
if !n.Resolve { if !n.Resolve {
valueNode = n.renderReference(lut.(low.IsReferenced)) valueNode = n.renderReference(lut.(low.IsReferenced))
break break

View File

@@ -155,6 +155,11 @@ func (d *Document) GoLow() *low.Document {
return d.low return d.low
} }
// GoLowUntyped returns the low-level Document that was used to create the high level one, however, it's untyped.
func (d *Document) GoLowUntyped() any {
return d.low
}
// Render will return a YAML representation of the Document object as a byte slice. // Render will return a YAML representation of the Document object as a byte slice.
func (d *Document) Render() ([]byte, error) { func (d *Document) Render() ([]byte, error) {
return yaml.Marshal(d) return yaml.Marshal(d)

View File

@@ -87,6 +87,7 @@ func TestNewDocument_Info(t *testing.T) {
assert.Equal(t, "1.2", highDoc.Info.Version) assert.Equal(t, "1.2", highDoc.Info.Version)
assert.Equal(t, "https://pb33f.io/schema", highDoc.JsonSchemaDialect) assert.Equal(t, "https://pb33f.io/schema", highDoc.JsonSchemaDialect)
assert.NotNil(t, highDoc.GoLowUntyped())
wentLow := highDoc.GoLow() wentLow := highDoc.GoLow()
assert.Equal(t, 1, wentLow.Version.ValueNode.Line) assert.Equal(t, 1, wentLow.Version.ValueNode.Line)
assert.Equal(t, 3, wentLow.Info.Value.Title.KeyNode.Line) assert.Equal(t, 3, wentLow.Info.Value.Title.KeyNode.Line)

View File

@@ -23,14 +23,14 @@ type Contact struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// Build is not implemented for Contact (there is nothing to build). func (c *Contact) Build(ctx context.Context, keyNode, root *yaml.Node, _ *index.SpecIndex) error {
func (c *Contact) Build(_ context.Context, keyNode, root *yaml.Node, _ *index.SpecIndex) error {
c.KeyNode = keyNode c.KeyNode = keyNode
c.RootNode = root c.RootNode = root
c.Reference = new(low.Reference) c.Reference = new(low.Reference)
// not implemented. c.Nodes = low.ExtractNodes(ctx, root)
return nil return nil
} }

View File

@@ -0,0 +1,30 @@
// Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package base
import (
"golang.org/x/net/context"
"sync"
)
// ModelContext is a struct that holds various persistent data structures for the model
// that passes through the entire model building process.
type ModelContext struct {
SchemaCache *sync.Map
}
// GetModelContext will return the ModelContext from a context.Context object
// if it is available, otherwise it will return nil.
func GetModelContext(ctx context.Context) *ModelContext {
if ctx == nil {
return nil
}
if ctx.Value("modelCtx") == nil {
return nil
}
if c, ok := ctx.Value("modelCtx").(*ModelContext); ok {
return c
}
return nil
}

View File

@@ -5,6 +5,7 @@ package base
import ( import (
"crypto/sha256" "crypto/sha256"
"gopkg.in/yaml.v3"
"strings" "strings"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
@@ -23,7 +24,20 @@ import (
type Discriminator struct { type Discriminator struct {
PropertyName low.NodeReference[string] PropertyName low.NodeReference[string]
Mapping low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]] Mapping low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[string]]]
KeyNode *yaml.Node
RootNode *yaml.Node
low.Reference low.Reference
low.NodeMap
}
// GetRootNode will return the root yaml node of the Discriminator object
func (d *Discriminator) GetRootNode() *yaml.Node {
return d.RootNode
}
// GetKeyNode will return the key yaml node of the Discriminator object
func (d *Discriminator) GetKeyNode() *yaml.Node {
return d.KeyNode
} }
// FindMappingValue will return a ValueReference containing the string mapping value // FindMappingValue will return a ValueReference containing the string mapping value

View File

@@ -28,6 +28,7 @@ type Example struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// FindExtension returns a ValueReference containing the extension value, if found. // FindExtension returns a ValueReference containing the extension value, if found.
@@ -67,12 +68,13 @@ func (ex *Example) Hash() [32]byte {
} }
// Build extracts extensions and example value // Build extracts extensions and example value
func (ex *Example) Build(_ context.Context, keyNode, root *yaml.Node, _ *index.SpecIndex) error { func (ex *Example) Build(ctx context.Context, keyNode, root *yaml.Node, _ *index.SpecIndex) error {
ex.KeyNode = keyNode ex.KeyNode = keyNode
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
ex.RootNode = root ex.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
ex.Reference = new(low.Reference) ex.Reference = new(low.Reference)
ex.Nodes = low.ExtractNodes(ctx, root)
ex.Extensions = low.ExtractExtensions(root) ex.Extensions = low.ExtractExtensions(root)
_, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content) _, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content)
@@ -82,6 +84,23 @@ func (ex *Example) Build(_ context.Context, keyNode, root *yaml.Node, _ *index.S
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
// extract nodes for all value nodes down the tree.
expChildNodes := low.ExtractNodesRecursive(ctx, vn)
expChildNodes.Range(func(k, v interface{}) bool {
if arr, ko := v.([]*yaml.Node); ko {
if ext, ok := ex.Nodes.Load(k); ok {
if extArr, kk := ext.([]*yaml.Node); kk {
ex.Nodes.Store(k, append(extArr, arr...))
} else {
ex.Nodes.Store(k, arr)
}
} else {
ex.Nodes.Store(k, arr)
}
}
return true
})
return nil return nil
} }
return nil return nil

View File

@@ -28,6 +28,7 @@ type ExternalDoc struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// FindExtension returns a ValueReference containing the extension value, if found. // FindExtension returns a ValueReference containing the extension value, if found.
@@ -46,12 +47,13 @@ func (ex *ExternalDoc) GetKeyNode() *yaml.Node {
} }
// Build will extract extensions from the ExternalDoc instance. // Build will extract extensions from the ExternalDoc instance.
func (ex *ExternalDoc) Build(_ context.Context, keyNode, root *yaml.Node, idx *index.SpecIndex) error { func (ex *ExternalDoc) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index.SpecIndex) error {
ex.KeyNode = keyNode ex.KeyNode = keyNode
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
ex.RootNode = root ex.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
ex.Reference = new(low.Reference) ex.Reference = new(low.Reference)
ex.Nodes = low.ExtractNodes(ctx, root)
ex.Extensions = low.ExtractExtensions(root) ex.Extensions = low.ExtractExtensions(root)
return nil return nil
} }

View File

@@ -35,6 +35,7 @@ type Info struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// FindExtension attempts to locate an extension with the supplied key // FindExtension attempts to locate an extension with the supplied key
@@ -64,6 +65,7 @@ func (i *Info) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index.S
i.RootNode = root i.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
i.Reference = new(low.Reference) i.Reference = new(low.Reference)
i.Nodes = low.ExtractNodes(ctx, root)
i.Extensions = low.ExtractExtensions(root) i.Extensions = low.ExtractExtensions(root)
// extract contact // extract contact

View File

@@ -25,6 +25,7 @@ type License struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// Build out a license, complain if both a URL and identifier are present as they are mutually exclusive // Build out a license, complain if both a URL and identifier are present as they are mutually exclusive
@@ -34,6 +35,8 @@ func (l *License) Build(ctx context.Context, keyNode, root *yaml.Node, idx *inde
l.RootNode = root l.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
l.Reference = new(low.Reference) l.Reference = new(low.Reference)
no := low.ExtractNodes(ctx, root)
l.Nodes = no
if l.URL.Value != "" && l.Identifier.Value != "" { if l.URL.Value != "" && l.Identifier.Value != "" {
return fmt.Errorf("license cannot have both a URL and an identifier, they are mutually exclusive") return fmt.Errorf("license cannot have both a URL and an identifier, they are mutually exclusive")
} }

View File

@@ -144,6 +144,7 @@ type Schema struct {
Index *index.SpecIndex Index *index.SpecIndex
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// Hash will calculate a SHA256 hash from the values of the schema, This allows equality checking against // Hash will calculate a SHA256 hash from the values of the schema, This allows equality checking against
@@ -470,8 +471,11 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference) s.Reference = new(low.Reference)
no := low.ExtractNodes(ctx, root)
s.Nodes = no
s.Index = idx s.Index = idx
s.RootNode = root s.RootNode = root
if h, _, _ := utils.IsNodeRefValue(root); h { if h, _, _ := utils.IsNodeRefValue(root); h {
ref, _, err, fctx := low.LocateRefNodeWithContext(ctx, root, idx) ref, _, err, fctx := low.LocateRefNodeWithContext(ctx, root, idx)
if ref != nil { if ref != nil {
@@ -497,6 +501,20 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
s.extractExtensions(root) s.extractExtensions(root)
// if the schema has required values, extract the nodes for them.
if s.Required.Value != nil {
for _, r := range s.Required.Value {
s.AddNode(r.ValueNode.Line, r.ValueNode)
}
}
// same thing with enums
if s.Enum.Value != nil {
for _, e := range s.Enum.Value {
s.AddNode(e.ValueNode.Line, e.ValueNode)
}
}
// determine schema type, singular (3.0) or multiple (3.1), use a variable value // determine schema type, singular (3.0) or multiple (3.1), use a variable value
_, typeLabel, typeValue := utils.FindKeyNodeFullTop(TypeLabel, root.Content) _, typeLabel, typeValue := utils.FindKeyNodeFullTop(TypeLabel, root.Content)
if typeValue != nil { if typeValue != nil {
@@ -630,6 +648,24 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
_, expLabel, expNode := utils.FindKeyNodeFullTop(ExampleLabel, root.Content) _, expLabel, expNode := utils.FindKeyNodeFullTop(ExampleLabel, root.Content)
if expNode != nil { if expNode != nil {
s.Example = low.NodeReference[*yaml.Node]{Value: expNode, KeyNode: expLabel, ValueNode: expNode} s.Example = low.NodeReference[*yaml.Node]{Value: expNode, KeyNode: expLabel, ValueNode: expNode}
// extract nodes for all value nodes down the tree.
expChildNodes := low.ExtractNodesRecursive(ctx, expNode)
// map to the local schema
expChildNodes.Range(func(k, v interface{}) bool {
if arr, ko := v.([]*yaml.Node); ko {
if ext, ok := s.Nodes.Load(k); ok {
if extArr, kk := ext.([]*yaml.Node); kk {
s.Nodes.Store(k, append(extArr, arr...))
} else {
s.Nodes.Store(k, arr)
}
} else {
s.Nodes.Store(k, arr)
}
}
return true
})
} }
// handle examples if set.(3.1) // handle examples if set.(3.1)
@@ -645,6 +681,23 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
ValueNode: expArrNode, ValueNode: expArrNode,
KeyNode: expArrLabel, KeyNode: expArrLabel,
} }
// extract nodes for all value nodes down the tree.
expChildNodes := low.ExtractNodesRecursive(ctx, expArrNode)
// map to the local schema
expChildNodes.Range(func(k, v interface{}) bool {
if arr, ko := v.([]*yaml.Node); ko {
if ext, ok := s.Nodes.Load(k); ok {
if extArr, kk := ext.([]*yaml.Node); kk {
s.Nodes.Store(k, append(extArr, arr...))
} else {
s.Nodes.Store(k, arr)
}
} else {
s.Nodes.Store(k, arr)
}
}
return true
})
} }
} }
@@ -674,7 +727,23 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
if discNode != nil { if discNode != nil {
var discriminator Discriminator var discriminator Discriminator
_ = low.BuildModel(discNode, &discriminator) _ = low.BuildModel(discNode, &discriminator)
discriminator.KeyNode = discLabel
discriminator.RootNode = discNode
discriminator.Nodes = low.ExtractNodes(ctx, discNode)
s.Discriminator = low.NodeReference[*Discriminator]{Value: &discriminator, KeyNode: discLabel, ValueNode: discNode} s.Discriminator = low.NodeReference[*Discriminator]{Value: &discriminator, KeyNode: discLabel, ValueNode: discNode}
// add discriminator nodes, because there is no build method.
dn := low.ExtractNodesRecursive(ctx, discNode)
dn.Range(func(key, val any) bool {
if n, ok := val.([]*yaml.Node); ok {
for _, g := range n {
discriminator.AddNode(key.(int), g)
}
}
if n, ok := val.(*yaml.Node); ok {
discriminator.AddNode(key.(int), n)
}
return true
})
} }
// handle externalDocs if set. // handle externalDocs if set.
@@ -683,6 +752,7 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
var exDoc ExternalDoc var exDoc ExternalDoc
_ = low.BuildModel(extDocNode, &exDoc) _ = low.BuildModel(extDocNode, &exDoc)
_ = exDoc.Build(ctx, extDocLabel, extDocNode, idx) // throws no errors, can't check for one. _ = exDoc.Build(ctx, extDocLabel, extDocNode, idx) // throws no errors, can't check for one.
exDoc.Nodes = low.ExtractNodes(ctx, extDocNode)
s.ExternalDocs = low.NodeReference[*ExternalDoc]{Value: &exDoc, KeyNode: extDocLabel, ValueNode: extDocNode} s.ExternalDocs = low.NodeReference[*ExternalDoc]{Value: &exDoc, KeyNode: extDocLabel, ValueNode: extDocNode}
} }
@@ -693,11 +763,12 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
_ = low.BuildModel(xmlNode, &xml) _ = low.BuildModel(xmlNode, &xml)
// extract extensions if set. // extract extensions if set.
_ = xml.Build(xmlNode, idx) // returns no errors, can't check for one. _ = xml.Build(xmlNode, idx) // returns no errors, can't check for one.
xml.Nodes = low.ExtractNodes(ctx, xmlNode)
s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode} s.XML = low.NodeReference[*XML]{Value: &xml, KeyNode: xmlLabel, ValueNode: xmlNode}
} }
// handle properties // handle properties
props, err := buildPropertyMap(ctx, root, idx, PropertiesLabel) props, err := buildPropertyMap(ctx, s, root, idx, PropertiesLabel)
if err != nil { if err != nil {
return err return err
} }
@@ -706,7 +777,7 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
} }
// handle dependent schemas // handle dependent schemas
props, err = buildPropertyMap(ctx, root, idx, DependentSchemasLabel) props, err = buildPropertyMap(ctx, s, root, idx, DependentSchemasLabel)
if err != nil { if err != nil {
return err return err
} }
@@ -715,7 +786,7 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
} }
// handle pattern properties // handle pattern properties
props, err = buildPropertyMap(ctx, root, idx, PatternPropertiesLabel) props, err = buildPropertyMap(ctx, s, root, idx, PatternPropertiesLabel)
if err != nil { if err != nil {
return err return err
} }
@@ -1013,7 +1084,7 @@ func (s *Schema) Build(ctx context.Context, root *yaml.Node, idx *index.SpecInde
return nil return nil
} }
func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]], error) { func buildPropertyMap(ctx context.Context, parent *Schema, root *yaml.Node, idx *index.SpecIndex, label string) (*low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*SchemaProxy]]], error) {
_, propLabel, propsNode := utils.FindKeyNodeFullTop(label, root.Content) _, propLabel, propsNode := utils.FindKeyNodeFullTop(label, root.Content)
if propsNode != nil { if propsNode != nil {
propertyMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*SchemaProxy]]() propertyMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*SchemaProxy]]()
@@ -1021,6 +1092,7 @@ func buildPropertyMap(ctx context.Context, root *yaml.Node, idx *index.SpecIndex
for i, prop := range propsNode.Content { for i, prop := range propsNode.Content {
if i%2 == 0 { if i%2 == 0 {
currentProp = prop currentProp = prop
parent.Nodes.Store(prop.Line, prop)
continue continue
} }

View File

@@ -7,6 +7,7 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"log/slog" "log/slog"
"sync"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
@@ -54,6 +55,7 @@ type SchemaProxy struct {
rendered *Schema rendered *Schema
buildError error buildError error
ctx context.Context ctx context.Context
*low.NodeMap
} }
// Build will prepare the SchemaProxy for rendering, it does not build the Schema, only sets up internal state. // Build will prepare the SchemaProxy for rendering, it does not build the Schema, only sets up internal state.
@@ -66,6 +68,8 @@ func (sp *SchemaProxy) Build(ctx context.Context, key, value *yaml.Node, idx *in
if rf, _, r := utils.IsNodeRefValue(value); rf { if rf, _, r := utils.IsNodeRefValue(value); rf {
sp.SetReference(r, value) sp.SetReference(r, value)
} }
var m sync.Map
sp.NodeMap = &low.NodeMap{Nodes: &m}
return nil return nil
} }
@@ -93,6 +97,14 @@ func (sp *SchemaProxy) Schema() *Schema {
} }
schema.ParentProxy = sp // https://github.com/pb33f/libopenapi/issues/29 schema.ParentProxy = sp // https://github.com/pb33f/libopenapi/issues/29
sp.rendered = schema sp.rendered = schema
// for all the nodes added, copy them over to the schema
if sp.NodeMap != nil {
sp.NodeMap.Nodes.Range(func(key, value any) bool {
schema.AddNode(key.(int), value.(*yaml.Node))
return true
})
}
return schema return schema
} }
@@ -158,3 +170,12 @@ func (sp *SchemaProxy) Hash() [32]byte {
// hash reference value only, do not resolve! // hash reference value only, do not resolve!
return sha256.Sum256([]byte(sp.GetReference())) return sha256.Sum256([]byte(sp.GetReference()))
} }
// AddNode stores nodes in the underlying schema if rendered, otherwise holds in the proxy until build.
func (sp *SchemaProxy) AddNode(key int, node *yaml.Node) {
if sp.rendered != nil {
sp.rendered.AddNode(key, node)
} else {
sp.Nodes.Store(key, node)
}
}

View File

@@ -31,15 +31,18 @@ type SecurityRequirement struct {
RootNode *yaml.Node RootNode *yaml.Node
ContainsEmptyRequirement bool // if a requirement is empty (this means it's optional) ContainsEmptyRequirement bool // if a requirement is empty (this means it's optional)
*low.Reference *low.Reference
low.NodeMap
} }
// Build will extract security requirements from the node (the structure is odd, to be honest) // Build will extract security requirements from the node (the structure is odd, to be honest)
func (s *SecurityRequirement) Build(_ context.Context, keyNode, root *yaml.Node, _ *index.SpecIndex) error { func (s *SecurityRequirement) Build(ctx context.Context, keyNode, root *yaml.Node, _ *index.SpecIndex) error {
s.KeyNode = keyNode s.KeyNode = keyNode
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
s.RootNode = root s.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference) s.Reference = new(low.Reference)
s.Nodes = low.ExtractNodes(ctx, root)
var labelNode *yaml.Node var labelNode *yaml.Node
valueMap := orderedmap.New[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]() valueMap := orderedmap.New[low.KeyReference[string], low.ValueReference[[]low.ValueReference[string]]]()
var arr []low.ValueReference[string] var arr []low.ValueReference[string]
@@ -57,6 +60,7 @@ func (s *SecurityRequirement) Build(_ context.Context, keyNode, root *yaml.Node,
Value: root.Content[i].Content[j].Value, Value: root.Content[i].Content[j].Value,
ValueNode: root.Content[i].Content[j], ValueNode: root.Content[i].Content[j],
}) })
s.Nodes.Store(root.Content[i].Content[j].Line, root.Content[i].Content[j])
} }
valueMap.Set( valueMap.Set(
low.KeyReference[string]{ low.KeyReference[string]{
@@ -76,6 +80,7 @@ func (s *SecurityRequirement) Build(_ context.Context, keyNode, root *yaml.Node,
Value: valueMap, Value: valueMap,
ValueNode: root, ValueNode: root,
} }
return nil return nil
} }

View File

@@ -29,6 +29,7 @@ type Tag struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// FindExtension returns a ValueReference containing the extension value, if found. // FindExtension returns a ValueReference containing the extension value, if found.
@@ -53,7 +54,9 @@ func (t *Tag) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index.Sp
t.RootNode = root t.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
t.Reference = new(low.Reference) t.Reference = new(low.Reference)
t.Nodes = low.ExtractNodes(ctx, root)
t.Extensions = low.ExtractExtensions(root) t.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, t.Extensions, t.Nodes)
// extract externalDocs // extract externalDocs
extDocs, err := low.ExtractObject[*ExternalDoc](ctx, ExternalDocsLabel, root, idx) extDocs, err := low.ExtractObject[*ExternalDoc](ctx, ExternalDocsLabel, root, idx)

View File

@@ -30,6 +30,7 @@ type XML struct {
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// Build will extract extensions from the XML instance. // Build will extract extensions from the XML instance.
@@ -38,6 +39,7 @@ func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error {
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
x.RootNode = root x.RootNode = root
x.Reference = new(low.Reference) x.Reference = new(low.Reference)
x.Nodes = low.ExtractNodes(nil, root)
x.Extensions = low.ExtractExtensions(root) x.Extensions = low.ExtractExtensions(root)
return nil return nil
} }

148
datamodel/low/node_map.go Normal file
View File

@@ -0,0 +1,148 @@
// Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
// MIT License
package low
import (
"context"
"github.com/pb33f/libopenapi/orderedmap"
"gopkg.in/yaml.v3"
"sync"
)
// HasNodes is an interface that defines a method to get a map of nodes
type HasNodes interface {
GetNodes() map[int][]*yaml.Node
}
// AddNodes is an interface that defined a method to add nodes.
type AddNodes interface {
AddNode(key int, node *yaml.Node)
}
// NodeMap represents a map of yaml nodes
type NodeMap struct {
// Nodes is a sync map of nodes for this object, and the key is the line number of the node
// a line can contain many nodes (in JSON), so the value is a slice of *yaml.Node
Nodes *sync.Map `yaml:"-" json:"-"`
}
// AddNode will add a node to the NodeMap
func (nm *NodeMap) AddNode(key int, node *yaml.Node) {
if existing, ok := nm.Nodes.Load(key); ok {
if ext, ko := existing.(*yaml.Node); ko {
nm.Nodes.Store(key, []*yaml.Node{ext, node})
}
if ext, ko := existing.([]*yaml.Node); ko {
ext = append(ext, node)
nm.Nodes.Store(key, ext)
}
} else {
nm.Nodes.Store(key, []*yaml.Node{node})
}
}
// GetNodes will return the map of nodes
func (nm *NodeMap) GetNodes() map[int][]*yaml.Node {
composed := make(map[int][]*yaml.Node)
nm.Nodes.Range(func(key, value interface{}) bool {
if v, ok := value.([]*yaml.Node); ok {
composed[key.(int)] = v
}
if v, ok := value.(*yaml.Node); ok {
composed[key.(int)] = []*yaml.Node{v}
}
return true
})
if len(composed) <= 0 {
composed[0] = []*yaml.Node{} // return an empty slice if there are no nodes
}
return composed
}
// ExtractNodes will iterate over a *yaml.Node and extract all nodes with a line number into a map
func (nm *NodeMap) ExtractNodes(node *yaml.Node, recurse bool) {
if node == nil {
return
}
// if the node has content, iterate over it and extract every top level line number
if node.Content != nil {
for i := 0; i < len(node.Content); i++ {
if node.Content[i].Line != 0 && len(node.Content[i].Content) <= 0 {
nm.AddNode(node.Content[i].Line, node.Content[i])
}
if node.Content[i].Line != 0 && len(node.Content[i].Content) > 0 {
if recurse {
nm.AddNode(node.Content[i].Line, node.Content[i])
nm.ExtractNodes(node.Content[i], recurse)
}
}
}
}
}
// ContainsLine will return true if the NodeMap contains a node with the supplied line number
func (nm *NodeMap) ContainsLine(line int, recurse bool) bool {
if _, ok := nm.Nodes.Load(line); ok {
return true
}
return false
}
// ExtractNodes will extract all nodes from a yaml.Node and return them in a map
func ExtractNodes(_ context.Context, root *yaml.Node) *sync.Map {
var syncMap sync.Map
nm := &NodeMap{Nodes: &syncMap}
nm.ExtractNodes(root, false)
return nm.Nodes
}
// ExtractNodesRecursive will extract all nodes from a yaml.Node and return them in a map, just like ExtractNodes
// however, this version will dive-down the tree and extract all nodes from all child nodes as well until the tree
// is done.
func ExtractNodesRecursive(_ context.Context, root *yaml.Node) *sync.Map {
var syncMap sync.Map
nm := &NodeMap{Nodes: &syncMap}
nm.ExtractNodes(root, true)
return nm.Nodes
}
// ExtractExtensionNodes will extract all extension nodes from a map of extensions, recursively.
func ExtractExtensionNodes(_ context.Context,
extensionMap *orderedmap.Map[KeyReference[string],
ValueReference[*yaml.Node]], nodeMap *sync.Map) {
// range over the extension map and extract all nodes
for extPairs := extensionMap.First(); extPairs != nil; extPairs = extPairs.Next() {
k := extPairs.Key()
v := extPairs.Value()
results := []*yaml.Node{k.KeyNode}
var newNodeMap sync.Map
nm := &NodeMap{Nodes: &newNodeMap}
if len(v.ValueNode.Content) > 0 {
nm.ExtractNodes(v.ValueNode, true)
nm.Nodes.Range(func(key, value interface{}) bool {
for _, n := range value.([]*yaml.Node) {
results = append(results, n)
}
return true
})
} else {
results = append(results, v.ValueNode)
}
if k.KeyNode.Line == v.ValueNode.Line {
nodeMap.Store(k.KeyNode.Line, results)
} else {
nodeMap.Store(k.KeyNode.Line, results[0])
gingo := results[1:]
for _, y := range gingo {
nodeMap.Store(y.Line, []*yaml.Node{y})
}
//nodeMap.Store(v.ValueNode.Line, gingo)
}
}
}

View File

@@ -0,0 +1,111 @@
// Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
package low
import (
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"sync"
"testing"
)
func Test_NodeMapExtractNodes(t *testing.T) {
yml := `one: hello
two: there
three: nice one
four:
shoes: yes
socks: of course
`
var root yaml.Node
_ = yaml.Unmarshal([]byte(yml), &root)
var syncMap sync.Map
nm := &NodeMap{Nodes: &syncMap}
nm.ExtractNodes(root.Content[0], false)
testTheThing(t, nm)
}
func testTheThing(t *testing.T, nm *NodeMap) {
count := 0
nm.Nodes.Range(func(key, value interface{}) bool {
count++
return true
})
assert.Equal(t, 4, count)
nodes := nm.GetNodes()
assert.Equal(t, 2, len(nodes[1]))
assert.Equal(t, 2, len(nodes[2]))
assert.Equal(t, 2, len(nodes[3]))
assert.Equal(t, 1, len(nodes[4]))
assert.Equal(t, "one", nodes[1][0].Value)
assert.Equal(t, "hello", nodes[1][1].Value)
assert.Equal(t, "two", nodes[2][0].Value)
assert.Equal(t, "there", nodes[2][1].Value)
assert.Equal(t, "three", nodes[3][0].Value)
assert.Equal(t, "nice one", nodes[3][1].Value)
assert.Equal(t, "four", nodes[4][0].Value)
}
func testTheThingUnmarshalled(t *testing.T, nm *sync.Map) {
n := &NodeMap{Nodes: nm}
nodes := n.GetNodes()
assert.Equal(t, 2, len(nodes[1]))
assert.Equal(t, 2, len(nodes[2]))
assert.Equal(t, 2, len(nodes[3]))
assert.Equal(t, 1, len(nodes[4]))
assert.Equal(t, "one", nodes[1][0].Value)
assert.Equal(t, "hello", nodes[1][1].Value)
assert.Equal(t, "two", nodes[2][0].Value)
assert.Equal(t, "there", nodes[2][1].Value)
assert.Equal(t, "three", nodes[3][0].Value)
assert.Equal(t, "nice one", nodes[3][1].Value)
assert.Equal(t, "four", nodes[4][0].Value)
}
func TestExtractNodes(t *testing.T) {
yml := `one: hello
two: there
three: nice one
four:
shoes: yes
socks: of course
`
var root yaml.Node
_ = yaml.Unmarshal([]byte(yml), &root)
nm := ExtractNodes(nil, root.Content[0])
count := 0
nm.Range(func(key, value interface{}) bool {
count++
return true
})
assert.Equal(t, 4, count)
testTheThingUnmarshalled(t, nm)
}
func TestExtractNodes_Nil(t *testing.T) {
var syncMap sync.Map
nm := &NodeMap{Nodes: &syncMap}
nm.ExtractNodes(nil, false)
count := 0
nm.Nodes.Range(func(key, value interface{}) bool {
count++
return true
})
assert.Equal(t, 0, count)
}

View File

@@ -29,6 +29,7 @@ type Callback struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetExtensions returns all Callback extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all Callback extensions and satisfies the low.HasExtensions interface.
@@ -58,14 +59,18 @@ func (cb *Callback) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
cb.RootNode = root cb.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
cb.Reference = new(low.Reference) cb.Reference = new(low.Reference)
cb.Nodes = low.ExtractNodes(ctx, root)
cb.Extensions = low.ExtractExtensions(root) cb.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, cb.Extensions, cb.Nodes)
expressions, err := extractPathItemsMap(ctx, root, idx) expressions, err := extractPathItemsMap(ctx, root, idx)
if err != nil { if err != nil {
return err return err
} }
cb.Expression = expressions cb.Expression = expressions
for xp := expressions.First(); xp != nil; xp = xp.Next() {
cb.Nodes.Store(xp.Key().KeyNode.Line, xp.Key().KeyNode)
}
return nil return nil
} }

View File

@@ -7,6 +7,7 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"reflect"
"strings" "strings"
"sync" "sync"
@@ -38,6 +39,7 @@ type Components struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
type componentBuildResult[T any] struct { type componentBuildResult[T any] struct {
@@ -139,7 +141,9 @@ func (co *Components) Build(ctx context.Context, root *yaml.Node, idx *index.Spe
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
co.Reference = new(low.Reference) co.Reference = new(low.Reference)
co.Nodes = low.ExtractNodes(ctx, root)
co.Extensions = low.ExtractExtensions(root) co.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, co.Extensions, co.Nodes)
co.RootNode = root co.RootNode = root
co.KeyNode = root co.KeyNode = root
var reterr error var reterr error
@@ -156,55 +160,55 @@ func (co *Components) Build(ctx context.Context, root *yaml.Node, idx *index.Spe
} }
go func() { go func() {
schemas, err := extractComponentValues[*base.SchemaProxy](ctx, SchemasLabel, root, idx) schemas, err := extractComponentValues[*base.SchemaProxy](ctx, SchemasLabel, root, idx, co)
captureError(err) captureError(err)
co.Schemas = schemas co.Schemas = schemas
wg.Done() wg.Done()
}() }()
go func() { go func() {
parameters, err := extractComponentValues[*Parameter](ctx, ParametersLabel, root, idx) parameters, err := extractComponentValues[*Parameter](ctx, ParametersLabel, root, idx, co)
captureError(err) captureError(err)
co.Parameters = parameters co.Parameters = parameters
wg.Done() wg.Done()
}() }()
go func() { go func() {
responses, err := extractComponentValues[*Response](ctx, ResponsesLabel, root, idx) responses, err := extractComponentValues[*Response](ctx, ResponsesLabel, root, idx, co)
captureError(err) captureError(err)
co.Responses = responses co.Responses = responses
wg.Done() wg.Done()
}() }()
go func() { go func() {
examples, err := extractComponentValues[*base.Example](ctx, base.ExamplesLabel, root, idx) examples, err := extractComponentValues[*base.Example](ctx, base.ExamplesLabel, root, idx, co)
captureError(err) captureError(err)
co.Examples = examples co.Examples = examples
wg.Done() wg.Done()
}() }()
go func() { go func() {
requestBodies, err := extractComponentValues[*RequestBody](ctx, RequestBodiesLabel, root, idx) requestBodies, err := extractComponentValues[*RequestBody](ctx, RequestBodiesLabel, root, idx, co)
captureError(err) captureError(err)
co.RequestBodies = requestBodies co.RequestBodies = requestBodies
wg.Done() wg.Done()
}() }()
go func() { go func() {
headers, err := extractComponentValues[*Header](ctx, HeadersLabel, root, idx) headers, err := extractComponentValues[*Header](ctx, HeadersLabel, root, idx, co)
captureError(err) captureError(err)
co.Headers = headers co.Headers = headers
wg.Done() wg.Done()
}() }()
go func() { go func() {
securitySchemes, err := extractComponentValues[*SecurityScheme](ctx, SecuritySchemesLabel, root, idx) securitySchemes, err := extractComponentValues[*SecurityScheme](ctx, SecuritySchemesLabel, root, idx, co)
captureError(err) captureError(err)
co.SecuritySchemes = securitySchemes co.SecuritySchemes = securitySchemes
wg.Done() wg.Done()
}() }()
go func() { go func() {
links, err := extractComponentValues[*Link](ctx, LinksLabel, root, idx) links, err := extractComponentValues[*Link](ctx, LinksLabel, root, idx, co)
captureError(err) captureError(err)
co.Links = links co.Links = links
wg.Done() wg.Done()
}() }()
go func() { go func() {
callbacks, err := extractComponentValues[*Callback](ctx, CallbacksLabel, root, idx) callbacks, err := extractComponentValues[*Callback](ctx, CallbacksLabel, root, idx, co)
captureError(err) captureError(err)
co.Callbacks = callbacks co.Callbacks = callbacks
wg.Done() wg.Done()
@@ -217,12 +221,13 @@ func (co *Components) Build(ctx context.Context, root *yaml.Node, idx *index.Spe
// extractComponentValues converts all the YAML nodes of a component type to // extractComponentValues converts all the YAML nodes of a component type to
// low level model. // low level model.
// Process each node in parallel. // Process each node in parallel.
func extractComponentValues[T low.Buildable[N], N any](ctx context.Context, label string, root *yaml.Node, idx *index.SpecIndex) (low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]], error) { func extractComponentValues[T low.Buildable[N], N any](ctx context.Context, label string, root *yaml.Node, idx *index.SpecIndex, co *Components) (low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]], error) {
var emptyResult low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]] var emptyResult low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]]
_, nodeLabel, nodeValue := utils.FindKeyNodeFullTop(label, root.Content) _, nodeLabel, nodeValue := utils.FindKeyNodeFullTop(label, root.Content)
if nodeValue == nil { if nodeValue == nil {
return emptyResult, nil return emptyResult, nil
} }
co.Nodes.Store(nodeLabel.Line, nodeLabel)
componentValues := orderedmap.New[low.KeyReference[string], low.ValueReference[T]]() componentValues := orderedmap.New[low.KeyReference[string], low.ValueReference[T]]()
if utils.IsNodeArray(nodeValue) { if utils.IsNodeArray(nodeValue) {
return emptyResult, fmt.Errorf("node is array, cannot be used in components: line %d, column %d", nodeValue.Line, nodeValue.Column) return emptyResult, fmt.Errorf("node is array, cannot be used in components: line %d, column %d", nodeValue.Line, nodeValue.Column)
@@ -298,6 +303,21 @@ func extractComponentValues[T low.Buildable[N], N any](ctx context.Context, labe
if err != nil { if err != nil {
return componentBuildResult[T]{}, err return componentBuildResult[T]{}, err
} }
nType := reflect.TypeOf(n)
nValue := reflect.ValueOf(n)
// Check if the type implements low.HasKeyNode
hasKeyNodeType := reflect.TypeOf((*low.HasKeyNode)(nil)).Elem()
if nType.Implements(hasKeyNodeType) {
r := nValue.Interface()
if h, ok := r.(low.HasKeyNode); ok {
if k, ko := r.(low.AddNodes); ko {
k.AddNode(h.GetKeyNode().Line, h.GetKeyNode())
}
}
}
return componentBuildResult[T]{ return componentBuildResult[T]{
key: low.KeyReference[string]{ key: low.KeyReference[string]{
KeyNode: currentLabel, KeyNode: currentLabel,
@@ -315,6 +335,10 @@ func extractComponentValues[T low.Buildable[N], N any](ctx context.Context, labe
return emptyResult, err return emptyResult, err
} }
//for rt := componentValues.First(); rt != nil; rt = rt.Next() {
//
//}
results := low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]]{ results := low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[T]]]{
KeyNode: nodeLabel, KeyNode: nodeLabel,
ValueNode: nodeValue, ValueNode: nodeValue,

View File

@@ -36,7 +36,7 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
} }
version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode} version = low.NodeReference[string]{Value: versionNode.Value, KeyNode: labelNode, ValueNode: versionNode}
doc := Document{Version: version} doc := Document{Version: version}
doc.Nodes = low.ExtractNodes(nil, info.RootNode.Content[0])
// create an index config and shadow the document configuration. // create an index config and shadow the document configuration.
idxConfig := index.CreateClosedAPIIndexConfig() idxConfig := index.CreateClosedAPIIndexConfig()
idxConfig.SpecInfo = info idxConfig.SpecInfo = info
@@ -130,7 +130,12 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
doc.Index = rolodex.GetRootIndex() doc.Index = rolodex.GetRootIndex()
var wg sync.WaitGroup var wg sync.WaitGroup
var cacheMap sync.Map
modelContext := base.ModelContext{SchemaCache: &cacheMap}
ctx := context.WithValue(context.Background(), "modelCtx", &modelContext)
doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0]) doc.Extensions = low.ExtractExtensions(info.RootNode.Content[0])
low.ExtractExtensionNodes(ctx, doc.Extensions, doc.Nodes)
// if set, extract jsonSchemaDialect (3.1) // if set, extract jsonSchemaDialect (3.1)
_, dialectLabel, dialectNode := utils.FindKeyNodeFull(JSONSchemaDialectLabel, info.RootNode.Content) _, dialectLabel, dialectNode := utils.FindKeyNodeFull(JSONSchemaDialectLabel, info.RootNode.Content)
@@ -161,8 +166,6 @@ func createDocument(info *datamodel.SpecInfo, config *datamodel.DocumentConfigur
extractWebhooks, extractWebhooks,
} }
ctx := context.Background()
wg.Add(len(extractionFuncs)) wg.Add(len(extractionFuncs))
if config.Logger != nil { if config.Logger != nil {
config.Logger.Debug("running extractions") config.Logger.Debug("running extractions")
@@ -310,6 +313,9 @@ func extractWebhooks(ctx context.Context, info *datamodel.SpecInfo, doc *Documen
KeyNode: hooksL, KeyNode: hooksL,
ValueNode: hooksN, ValueNode: hooksN,
} }
for xj := hooks.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
} }
return nil return nil
} }

View File

@@ -86,6 +86,8 @@ type Document struct {
// Rolodex is a reference to the rolodex used when creating this document. // Rolodex is a reference to the rolodex used when creating this document.
Rolodex *index.Rolodex Rolodex *index.Rolodex
low.NodeMap
} }
// FindSecurityRequirement will attempt to locate a security requirement string from a supplied name. // FindSecurityRequirement will attempt to locate a security requirement string from a supplied name.

View File

@@ -27,6 +27,7 @@ type Encoding struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// FindHeader attempts to locate a Header with the supplied name // FindHeader attempts to locate a Header with the supplied name
@@ -67,6 +68,7 @@ func (en *Encoding) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
en.RootNode = root en.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
en.Nodes = low.ExtractNodes(ctx, root)
en.Reference = new(low.Reference) en.Reference = new(low.Reference)
headers, hL, hN, err := low.ExtractMap[*Header](ctx, HeadersLabel, root, idx) headers, hL, hN, err := low.ExtractMap[*Header](ctx, HeadersLabel, root, idx)
if err != nil { if err != nil {
@@ -78,6 +80,10 @@ func (en *Encoding) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
KeyNode: hL, KeyNode: hL,
ValueNode: hN, ValueNode: hN,
} }
en.Nodes.Store(hL.Line, hL)
for xj := headers.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
} }
return nil return nil
} }

View File

@@ -35,6 +35,7 @@ type Header struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// FindExtension will attempt to locate an extension with the supplied name // FindExtension will attempt to locate an extension with the supplied name
@@ -104,8 +105,9 @@ func (h *Header) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index
h.RootNode = root h.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
h.Reference = new(low.Reference) h.Reference = new(low.Reference)
h.Nodes = low.ExtractNodes(ctx, root)
h.Extensions = low.ExtractExtensions(root) h.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, h.Extensions, h.Nodes)
// handle example if set. // handle example if set.
_, expLabel, expNode := utils.FindKeyNodeFull(base.ExampleLabel, root.Content) _, expLabel, expNode := utils.FindKeyNodeFull(base.ExampleLabel, root.Content)
if expNode != nil { if expNode != nil {
@@ -114,6 +116,12 @@ func (h *Header) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index
ValueNode: expNode, ValueNode: expNode,
KeyNode: expLabel, KeyNode: expLabel,
} }
h.Nodes.Store(expLabel.Line, expLabel)
m := low.ExtractNodes(ctx, expNode)
m.Range(func(key, value any) bool {
h.Nodes.Store(key, value)
return true
})
} }
// handle examples if set. // handle examples if set.
@@ -127,6 +135,7 @@ func (h *Header) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index
KeyNode: expsL, KeyNode: expsL,
ValueNode: expsN, ValueNode: expsN,
} }
h.Nodes.Store(expsL.Line, expsL)
} }
// handle schema // handle schema
@@ -148,6 +157,9 @@ func (h *Header) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index
KeyNode: cL, KeyNode: cL,
ValueNode: cN, ValueNode: cN,
} }
if cL != nil {
h.Nodes.Store(cL.Line, cL)
}
return nil return nil
} }

View File

@@ -38,6 +38,7 @@ type Link struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetExtensions returns all Link extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all Link extensions and satisfies the low.HasExtensions interface.
@@ -72,7 +73,17 @@ func (l *Link) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index.S
l.RootNode = root l.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
l.Reference = new(low.Reference) l.Reference = new(low.Reference)
l.Nodes = low.ExtractNodes(ctx, root)
l.Extensions = low.ExtractExtensions(root) l.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, l.Extensions, l.Nodes)
// extract parameter nodes.
if l.Parameters.Value != nil && l.Parameters.Value.Len() > 0 {
for fk := l.Parameters.Value.First(); fk != nil; fk = fk.Next() {
l.Nodes.Store(fk.Key().KeyNode.Line, fk.Key().KeyNode)
}
}
// extract server. // extract server.
ser, sErr := low.ExtractObject[*Server](ctx, ServerLabel, root, idx) ser, sErr := low.ExtractObject[*Server](ctx, ServerLabel, root, idx)
if sErr != nil { if sErr != nil {

View File

@@ -30,6 +30,7 @@ type MediaType struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetExtensions returns all MediaType extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all MediaType extensions and satisfies the low.HasExtensions interface.
@@ -74,12 +75,20 @@ func (mt *MediaType) Build(ctx context.Context, keyNode, root *yaml.Node, idx *i
mt.RootNode = root mt.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
mt.Reference = new(low.Reference) mt.Reference = new(low.Reference)
mt.Nodes = low.ExtractNodes(ctx, root)
mt.Extensions = low.ExtractExtensions(root) mt.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, mt.Extensions, mt.Nodes)
// handle example if set. // handle example if set.
_, expLabel, expNode := utils.FindKeyNodeFullTop(base.ExampleLabel, root.Content) _, expLabel, expNode := utils.FindKeyNodeFullTop(base.ExampleLabel, root.Content)
if expNode != nil { if expNode != nil {
mt.Example = low.NodeReference[*yaml.Node]{Value: expNode, KeyNode: expLabel, ValueNode: expNode} mt.Example = low.NodeReference[*yaml.Node]{Value: expNode, KeyNode: expLabel, ValueNode: expNode}
mt.Nodes.Store(expLabel.Line, expLabel)
m := low.ExtractNodesRecursive(ctx, expNode)
m.Range(func(key, value any) bool {
mt.Nodes.Store(key, value)
return true
})
} }
// handle schema // handle schema
@@ -97,11 +106,16 @@ func (mt *MediaType) Build(ctx context.Context, keyNode, root *yaml.Node, idx *i
return eErr return eErr
} }
if exps != nil && slices.Contains(root.Content, expsL) { if exps != nil && slices.Contains(root.Content, expsL) {
mt.Nodes.Store(expsL.Line, expsL)
for xj := exps.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
mt.Examples = low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]]]{ mt.Examples = low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*base.Example]]]{
Value: exps, Value: exps,
KeyNode: expsL, KeyNode: expsL,
ValueNode: expsN, ValueNode: expsN,
} }
} }
// handle encoding // handle encoding
@@ -115,6 +129,10 @@ func (mt *MediaType) Build(ctx context.Context, keyNode, root *yaml.Node, idx *i
KeyNode: encsL, KeyNode: encsL,
ValueNode: encsN, ValueNode: encsN,
} }
mt.Nodes.Store(encsL.Line, encsL)
for xj := encs.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
} }
return nil return nil
} }

View File

@@ -27,6 +27,7 @@ type OAuthFlows struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetExtensions returns all OAuthFlows extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all OAuthFlows extensions and satisfies the low.HasExtensions interface.
@@ -56,6 +57,7 @@ func (o *OAuthFlows) Build(ctx context.Context, keyNode, root *yaml.Node, idx *i
o.RootNode = root o.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
o.Reference = new(low.Reference) o.Reference = new(low.Reference)
o.Nodes = low.ExtractNodes(ctx, root)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)
v, vErr := low.ExtractObject[*OAuthFlow](ctx, ImplicitLabel, root, idx) v, vErr := low.ExtractObject[*OAuthFlow](ctx, ImplicitLabel, root, idx)
@@ -113,6 +115,7 @@ type OAuthFlow struct {
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetExtensions returns all OAuthFlow extensions and satisfies the low.HasExtensions interface. // GetExtensions returns all OAuthFlow extensions and satisfies the low.HasExtensions interface.
@@ -136,9 +139,18 @@ func (o *OAuthFlow) GetRootNode() *yaml.Node {
} }
// Build will extract extensions from the node. // Build will extract extensions from the node.
func (o *OAuthFlow) Build(_ context.Context, _, root *yaml.Node, idx *index.SpecIndex) error { func (o *OAuthFlow) Build(ctx context.Context, _, root *yaml.Node, idx *index.SpecIndex) error {
o.Reference = new(low.Reference) o.Reference = new(low.Reference)
o.Nodes = low.ExtractNodes(ctx, root)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, o.Extensions, o.Nodes)
if o.Scopes.Value != nil && o.Scopes.Value.Len() > 0 {
for fk := o.Scopes.Value.First(); fk != nil; fk = fk.Next() {
o.Nodes.Store(fk.Key().KeyNode.Line, fk.Key().KeyNode)
}
}
o.RootNode = root o.RootNode = root
return nil return nil
} }

View File

@@ -40,6 +40,7 @@ type Operation struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// FindCallback will attempt to locate a Callback instance by the supplied name. // FindCallback will attempt to locate a Callback instance by the supplied name.
@@ -77,7 +78,9 @@ func (o *Operation) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
o.Reference = new(low.Reference) o.Reference = new(low.Reference)
o.Nodes = low.ExtractNodes(ctx, root)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, o.Extensions, o.Nodes)
// extract externalDocs // extract externalDocs
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, root, idx) extDocs, dErr := low.ExtractObject[*base.ExternalDoc](ctx, base.ExternalDocsLabel, root, idx)
@@ -97,6 +100,7 @@ func (o *Operation) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
o.Nodes.Store(ln.Line, ln)
} }
// extract request body // extract request body
@@ -106,6 +110,17 @@ func (o *Operation) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
} }
o.RequestBody = rBody o.RequestBody = rBody
// extract tags, but only extract nodes, the model has already been built
k, v := utils.FindKeyNode(TagsLabel, root.Content)
if k != nil && v != nil {
o.Nodes.Store(k.Line, k)
nm := low.ExtractNodesRecursive(ctx, v)
nm.Range(func(key, value interface{}) bool {
o.Nodes.Store(key, value)
return true
})
}
// extract responses // extract responses
respBody, respErr := low.ExtractObject[*Responses](ctx, ResponsesLabel, root, idx) respBody, respErr := low.ExtractObject[*Responses](ctx, ResponsesLabel, root, idx)
if respErr != nil { if respErr != nil {
@@ -124,6 +139,10 @@ func (o *Operation) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
KeyNode: cbL, KeyNode: cbL,
ValueNode: cbN, ValueNode: cbN,
} }
o.Nodes.Store(cbL.Line, cbL)
for xj := callbacks.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
} }
// extract security // extract security
@@ -139,6 +158,7 @@ func (o *Operation) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
KeyNode: sln, KeyNode: sln,
ValueNode: svn, ValueNode: svn,
} }
o.Nodes.Store(sln.Line, sln)
} }
// if security is set, but no requirements are defined. // if security is set, but no requirements are defined.
@@ -149,6 +169,7 @@ func (o *Operation) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
KeyNode: sln, KeyNode: sln,
ValueNode: svn, ValueNode: svn,
} }
o.Nodes.Store(sln.Line, svn)
} }
// extract servers // extract servers
@@ -162,6 +183,7 @@ func (o *Operation) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
KeyNode: sl, KeyNode: sl,
ValueNode: sn, ValueNode: sn,
} }
o.Nodes.Store(sl.Line, sl)
} }
return nil return nil
} }

View File

@@ -40,6 +40,7 @@ type Parameter struct {
Content low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]] Content low.NodeReference[*orderedmap.Map[low.KeyReference[string], low.ValueReference[*MediaType]]]
Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]] Extensions *orderedmap.Map[low.KeyReference[string], low.ValueReference[*yaml.Node]]
*low.Reference *low.Reference
low.NodeMap
} }
// GetRootNode returns the root yaml node of the Parameter object. // GetRootNode returns the root yaml node of the Parameter object.
@@ -79,12 +80,15 @@ func (p *Parameter) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
p.RootNode = root p.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
p.Reference = new(low.Reference) p.Reference = new(low.Reference)
p.Nodes = low.ExtractNodes(ctx, root)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, p.Extensions, p.Nodes)
// handle example if set. // handle example if set.
_, expLabel, expNode := utils.FindKeyNodeFullTop(base.ExampleLabel, root.Content) _, expLabel, expNode := utils.FindKeyNodeFullTop(base.ExampleLabel, root.Content)
if expNode != nil { if expNode != nil {
p.Example = low.NodeReference[*yaml.Node]{Value: expNode, KeyNode: expLabel, ValueNode: expNode} p.Example = low.NodeReference[*yaml.Node]{Value: expNode, KeyNode: expLabel, ValueNode: expNode}
p.Nodes.Store(expLabel.Line, expLabel)
} }
// handle schema // handle schema
@@ -108,6 +112,10 @@ func (p *Parameter) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
KeyNode: expsL, KeyNode: expsL,
ValueNode: expsN, ValueNode: expsN,
} }
p.Nodes.Store(expsL.Line, expsL)
for xj := exps.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
} }
// handle content, if set. // handle content, if set.
@@ -120,6 +128,13 @@ func (p *Parameter) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
KeyNode: cL, KeyNode: cL,
ValueNode: cN, ValueNode: cN,
} }
if cL != nil {
p.Nodes.Store(cL.Line, cL)
for xj := con.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
}
return nil return nil
} }

View File

@@ -42,6 +42,7 @@ type PathItem struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// Hash will return a consistent SHA256 Hash of the PathItem object // Hash will return a consistent SHA256 Hash of the PathItem object
@@ -121,13 +122,14 @@ func (p *PathItem) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind
p.RootNode = root p.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
p.Reference = new(low.Reference) p.Reference = new(low.Reference)
p.Nodes = low.ExtractNodes(ctx, root)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, p.Extensions, p.Nodes)
skip := false skip := false
var currentNode *yaml.Node var currentNode *yaml.Node
var wg sync.WaitGroup var wg sync.WaitGroup
var errors []error var errors []error
var ops []low.NodeReference[*Operation] var ops []low.NodeReference[*Operation]
// extract parameters // extract parameters
@@ -141,6 +143,7 @@ func (p *PathItem) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
p.Nodes.Store(ln.Line, ln)
} }
_, ln, vn = utils.FindKeyNodeFullTop(ServersLabel, root.Content) _, ln, vn = utils.FindKeyNodeFullTop(ServersLabel, root.Content)
@@ -163,6 +166,7 @@ func (p *PathItem) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind
KeyNode: ln, KeyNode: ln,
ValueNode: vn, ValueNode: vn,
} }
p.Nodes.Store(ln.Line, ln)
} }
} }

View File

@@ -30,6 +30,7 @@ type Paths struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetRootNode returns the root yaml node of the Paths object. // GetRootNode returns the root yaml node of the Paths object.
@@ -82,7 +83,9 @@ func (p *Paths) Build(ctx context.Context, keyNode, root *yaml.Node, idx *index.
p.RootNode = root p.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
p.Reference = new(low.Reference) p.Reference = new(low.Reference)
p.Nodes = low.ExtractNodes(ctx, root)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, p.Extensions, p.Nodes)
pathsMap, err := extractPathItemsMap(ctx, root, idx) pathsMap, err := extractPathItemsMap(ctx, root, idx)
if err != nil { if err != nil {

View File

@@ -26,6 +26,7 @@ type RequestBody struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetRootNode returns the root yaml node of the RequestBody object. // GetRootNode returns the root yaml node of the RequestBody object.
@@ -60,7 +61,9 @@ func (rb *RequestBody) Build(ctx context.Context, keyNode, root *yaml.Node, idx
rb.RootNode = root rb.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
rb.Reference = new(low.Reference) rb.Reference = new(low.Reference)
rb.Nodes = low.ExtractNodes(ctx, root)
rb.Extensions = low.ExtractExtensions(root) rb.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, rb.Extensions, rb.Nodes)
// handle content, if set. // handle content, if set.
con, cL, cN, cErr := low.ExtractMap[*MediaType](ctx, ContentLabel, root, idx) con, cL, cN, cErr := low.ExtractMap[*MediaType](ctx, ContentLabel, root, idx)
@@ -73,6 +76,10 @@ func (rb *RequestBody) Build(ctx context.Context, keyNode, root *yaml.Node, idx
KeyNode: cL, KeyNode: cL,
ValueNode: cN, ValueNode: cN,
} }
rb.Nodes.Store(cL.Line, cL)
for xj := con.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
} }
return nil return nil
} }

View File

@@ -30,6 +30,7 @@ type Response struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetRootNode returns the root yaml node of the Response object. // GetRootNode returns the root yaml node of the Response object.
@@ -74,7 +75,9 @@ func (r *Response) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind
r.RootNode = root r.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
r.Reference = new(low.Reference) r.Reference = new(low.Reference)
r.Nodes = low.ExtractNodes(ctx, root)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, r.Extensions, r.Nodes)
// extract headers // extract headers
headers, lN, kN, err := low.ExtractMapExtensions[*Header](ctx, HeadersLabel, root, idx, true) headers, lN, kN, err := low.ExtractMapExtensions[*Header](ctx, HeadersLabel, root, idx, true)
@@ -87,6 +90,10 @@ func (r *Response) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind
KeyNode: lN, KeyNode: lN,
ValueNode: kN, ValueNode: kN,
} }
r.Nodes.Store(lN.Line, lN)
for xj := headers.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
} }
con, clN, cN, cErr := low.ExtractMap[*MediaType](ctx, ContentLabel, root, idx) con, clN, cN, cErr := low.ExtractMap[*MediaType](ctx, ContentLabel, root, idx)
@@ -99,6 +106,10 @@ func (r *Response) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind
KeyNode: clN, KeyNode: clN,
ValueNode: cN, ValueNode: cN,
} }
r.Nodes.Store(clN.Line, clN)
for xj := con.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
} }
// handle links if set // handle links if set
@@ -112,6 +123,10 @@ func (r *Response) Build(ctx context.Context, keyNode, root *yaml.Node, idx *ind
KeyNode: linkLabel, KeyNode: linkLabel,
ValueNode: linkValue, ValueNode: linkValue,
} }
r.Nodes.Store(linkLabel.Line, linkLabel)
for xj := links.First(); xj != nil; xj = xj.Next() {
xj.Value().Value.Nodes.Store(xj.Key().KeyNode.Line, xj.Key().KeyNode)
}
} }
return nil return nil
} }

View File

@@ -41,6 +41,7 @@ type Responses struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetRootNode returns the root yaml node of the Responses object. // GetRootNode returns the root yaml node of the Responses object.
@@ -64,7 +65,9 @@ func (r *Responses) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
r.RootNode = root r.RootNode = root
r.Reference = new(low.Reference) r.Reference = new(low.Reference)
r.Nodes = low.ExtractNodes(ctx, root)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, r.Extensions, r.Nodes)
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {
codes, err := low.ExtractMapNoLookup[*Response](ctx, root, idx) codes, err := low.ExtractMapNoLookup[*Response](ctx, root, idx)
@@ -73,12 +76,17 @@ func (r *Responses) Build(ctx context.Context, keyNode, root *yaml.Node, idx *in
} }
if codes != nil { if codes != nil {
r.Codes = codes r.Codes = codes
for codePairs := codes.First(); codePairs != nil; codePairs = codePairs.Next() {
code := codePairs.Key()
r.Nodes.Store(code.KeyNode.Line, code.KeyNode)
}
} }
def := r.getDefault() def := r.getDefault()
if def != nil { if def != nil {
// default is bundled into codes, pull it out // default is bundled into codes, pull it out
r.Default = *def r.Default = *def
r.Nodes.Store(def.KeyNode.Line, def.KeyNode)
// remove default from codes // remove default from codes
r.deleteCode(DefaultLabel) r.deleteCode(DefaultLabel)
} }

View File

@@ -38,6 +38,7 @@ type SecurityScheme struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetRootNode returns the root yaml node of the SecurityScheme object. // GetRootNode returns the root yaml node of the SecurityScheme object.
@@ -67,7 +68,9 @@ func (ss *SecurityScheme) Build(ctx context.Context, keyNode, root *yaml.Node, i
ss.RootNode = root ss.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
ss.Reference = new(low.Reference) ss.Reference = new(low.Reference)
ss.Nodes = low.ExtractNodes(ctx, root)
ss.Extensions = low.ExtractExtensions(root) ss.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, ss.Extensions, ss.Nodes)
oa, oaErr := low.ExtractObject[*OAuthFlows](ctx, OAuthFlowsLabel, root, idx) oa, oaErr := low.ExtractObject[*OAuthFlows](ctx, OAuthFlowsLabel, root, idx)
if oaErr != nil { if oaErr != nil {

View File

@@ -25,6 +25,7 @@ type Server struct {
KeyNode *yaml.Node KeyNode *yaml.Node
RootNode *yaml.Node RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
} }
// GetRootNode returns the root yaml node of the Server object. // GetRootNode returns the root yaml node of the Server object.
@@ -43,13 +44,16 @@ func (s *Server) FindVariable(serverVar string) *low.ValueReference[*ServerVaria
} }
// Build will extract server variables from the supplied node. // Build will extract server variables from the supplied node.
func (s *Server) Build(_ context.Context, keyNode, root *yaml.Node, _ *index.SpecIndex) error { func (s *Server) Build(ctx context.Context, keyNode, root *yaml.Node, _ *index.SpecIndex) error {
s.KeyNode = keyNode s.KeyNode = keyNode
root = utils.NodeAlias(root) root = utils.NodeAlias(root)
s.RootNode = root s.RootNode = root
utils.CheckForMergeNodes(root) utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference) s.Reference = new(low.Reference)
s.Nodes = low.ExtractNodes(ctx, root)
s.Extensions = low.ExtractExtensions(root) s.Extensions = low.ExtractExtensions(root)
low.ExtractExtensionNodes(ctx, s.Extensions, s.Nodes)
kn, vars := utils.FindKeyNode(VariablesLabel, root.Content) kn, vars := utils.FindKeyNode(VariablesLabel, root.Content)
if vars == nil { if vars == nil {
return nil return nil
@@ -57,20 +61,26 @@ func (s *Server) Build(_ context.Context, keyNode, root *yaml.Node, _ *index.Spe
variablesMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*ServerVariable]]() variablesMap := orderedmap.New[low.KeyReference[string], low.ValueReference[*ServerVariable]]()
if utils.IsNodeMap(vars) { if utils.IsNodeMap(vars) {
var currentNode string var currentNode string
var keyNode *yaml.Node var localKeyNode *yaml.Node
for i, varNode := range vars.Content { for i, varNode := range vars.Content {
if i%2 == 0 { if i%2 == 0 {
currentNode = varNode.Value currentNode = varNode.Value
keyNode = varNode localKeyNode = varNode
continue continue
} }
variable := ServerVariable{} variable := ServerVariable{}
variable.Reference = new(low.Reference) variable.Reference = new(low.Reference)
_ = low.BuildModel(varNode, &variable) _ = low.BuildModel(varNode, &variable)
variable.Nodes = low.ExtractNodesRecursive(ctx, varNode)
if localKeyNode != nil {
variable.Nodes.Store(localKeyNode.Line, localKeyNode)
}
variable.RootNode = varNode
variable.KeyNode = localKeyNode
variablesMap.Set( variablesMap.Set(
low.KeyReference[string]{ low.KeyReference[string]{
Value: currentNode, Value: currentNode,
KeyNode: keyNode, KeyNode: localKeyNode,
}, },
low.ValueReference[*ServerVariable]{ low.ValueReference[*ServerVariable]{
ValueNode: varNode, ValueNode: varNode,

View File

@@ -4,6 +4,7 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
) )
@@ -19,7 +20,20 @@ type ServerVariable struct {
Enum []low.NodeReference[string] Enum []low.NodeReference[string]
Default low.NodeReference[string] Default low.NodeReference[string]
Description low.NodeReference[string] Description low.NodeReference[string]
KeyNode *yaml.Node
RootNode *yaml.Node
*low.Reference *low.Reference
low.NodeMap
}
// GetRootNode returns the root yaml node of the ServerVariable object.
func (s *ServerVariable) GetRootNode() *yaml.Node {
return s.RootNode
}
// GetKeyNode returns the key yaml node of the ServerVariable object.
func (s *ServerVariable) GetKeyNode() *yaml.Node {
return s.RootNode
} }
// Hash will return a consistent SHA256 Hash of the ServerVariable object // Hash will return a consistent SHA256 Hash of the ServerVariable object

View File

@@ -22,6 +22,7 @@ const (
// used by the library, this contains the top of the document tree that every single low model is based off. // used by the library, this contains the top of the document tree that every single low model is based off.
type SpecInfo struct { type SpecInfo struct {
SpecType string `json:"type"` SpecType string `json:"type"`
NumLines int `json:"numLines"`
Version string `json:"version"` Version string `json:"version"`
VersionNumeric float32 `json:"versionNumeric"` VersionNumeric float32 `json:"versionNumeric"`
SpecFormat string `json:"format"` SpecFormat string `json:"format"`
@@ -62,7 +63,8 @@ func ExtractSpecInfoWithDocumentCheck(spec []byte, bypass bool) (*SpecInfo, erro
// set original bytes // set original bytes
specInfo.SpecBytes = &spec specInfo.SpecBytes = &spec
runes := []rune(strings.TrimSpace(string(spec))) stringSpec := string(spec)
runes := []rune(strings.TrimSpace(stringSpec))
if len(runes) <= 0 { if len(runes) <= 0 {
return specInfo, errors.New("there is nothing in the spec, it's empty - so there is nothing to be done") return specInfo, errors.New("there is nothing in the spec, it's empty - so there is nothing to be done")
} }
@@ -73,6 +75,8 @@ func ExtractSpecInfoWithDocumentCheck(spec []byte, bypass bool) (*SpecInfo, erro
specInfo.SpecFileType = YAMLFileType specInfo.SpecFileType = YAMLFileType
} }
specInfo.NumLines = strings.Count(stringSpec, "\n") + 1
err := yaml.Unmarshal(spec, &parsedSpec) err := yaml.Unmarshal(spec, &parsedSpec)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse specification: %s", err.Error()) return nil, fmt.Errorf("unable to parse specification: %s", err.Error())

View File

@@ -365,7 +365,7 @@ func TestDocument_RenderAndReload_WithErrors(t *testing.T) {
_, _, _, errors := doc.RenderAndReload() _, _, _, errors := doc.RenderAndReload()
assert.Len(t, errors, 2) assert.Len(t, errors, 2)
assert.Equal(t, errors[0].Error(), "component '#/components/schemas/Pet' does not exist in the specification") assert.Equal(t, errors[0].Error(), "component `#/components/schemas/Pet` does not exist in the specification")
} }
func TestDocument_Render(t *testing.T) { func TestDocument_Render(t *testing.T) {

1
go.mod
View File

@@ -7,6 +7,7 @@ require (
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/vmware-labs/yaml-jsonpath v0.3.2 github.com/vmware-labs/yaml-jsonpath v0.3.2
github.com/wk8/go-ordered-map/v2 v2.1.8 github.com/wk8/go-ordered-map/v2 v2.1.8
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )

View File

@@ -274,7 +274,7 @@ paths:
index := NewSpecIndexWithConfig(&rootNode, c) index := NewSpecIndexWithConfig(&rootNode, c)
assert.Len(t, index.GetReferenceIndexErrors(), 1) assert.Len(t, index.GetReferenceIndexErrors(), 1)
assert.Equal(t, "component '#/paths/~1pet~1%$petId%7D/get/parameters' does not exist in the specification", index.GetReferenceIndexErrors()[0].Error()) assert.Equal(t, "component `#/paths/~1pet~1%$petId%7D/get/parameters` does not exist in the specification", index.GetReferenceIndexErrors()[0].Error())
} }
func TestSpecIndex_LocateRemoteDocsWithEscapedCharacters(t *testing.T) { func TestSpecIndex_LocateRemoteDocsWithEscapedCharacters(t *testing.T) {

View File

@@ -121,6 +121,11 @@ func (r *Rolodex) GetRootIndex() *SpecIndex {
return r.rootIndex return r.rootIndex
} }
// GetConfig returns the index configuration of the rolodex.
func (r *Rolodex) GetConfig() *SpecIndexConfig {
return r.indexConfig
}
// GetRootNode returns the root index of the rolodex (the entry point, the main document) // GetRootNode returns the root index of the rolodex (the entry point, the main document)
func (r *Rolodex) GetRootNode() *yaml.Node { func (r *Rolodex) GetRootNode() *yaml.Node {
return r.rootNode return r.rootNode

View File

@@ -31,6 +31,7 @@ func TestRolodex_NewRolodex(t *testing.T) {
assert.Nil(t, rolo.GetRootIndex()) assert.Nil(t, rolo.GetRootIndex())
assert.Len(t, rolo.GetIndexes(), 0) assert.Len(t, rolo.GetIndexes(), 0)
assert.Len(t, rolo.GetCaughtErrors(), 0) assert.Len(t, rolo.GetCaughtErrors(), 0)
assert.NotNil(t, rolo.GetConfig())
} }
func TestRolodex_NoFS(t *testing.T) { func TestRolodex_NoFS(t *testing.T) {

View File

@@ -613,6 +613,12 @@ func IsJSON(testString string) bool {
} }
// IsYAML will tell you if a string is YAML or not. // IsYAML will tell you if a string is YAML or not.
var (
yamlKeyValuePattern = regexp.MustCompile(`(?m)^\s*[a-zA-Z0-9_-]+\s*:\s*.+$`)
yamlListPattern = regexp.MustCompile(`(?m)^\s*-\s+.+$`)
yamlHeaderPattern = regexp.MustCompile(`(?m)^---\s*$`)
)
func IsYAML(testString string) bool { func IsYAML(testString string) bool {
if testString == "" { if testString == "" {
return false return false
@@ -620,13 +626,21 @@ func IsYAML(testString string) bool {
if IsJSON(testString) { if IsJSON(testString) {
return false return false
} }
var n interface{}
err := yaml.Unmarshal([]byte(testString), &n) // Trim leading and trailing whitespace
if err != nil { s := strings.TrimSpace(testString)
return false
// Fast checks for common YAML features
if strings.Contains(s, ": ") || strings.Contains(s, "- ") || strings.Contains(s, "\n- ") {
return true
} }
_, err = yaml.Marshal(n)
return err == nil // Regular expressions for more robust detection
if yamlKeyValuePattern.MatchString(s) || yamlListPattern.MatchString(s) || yamlHeaderPattern.MatchString(s) {
return true
}
return false
} }
// ConvertYAMLtoJSON will do exactly what you think it will. It will deserialize YAML into serialized JSON. // ConvertYAMLtoJSON will do exactly what you think it will. It will deserialize YAML into serialized JSON.

View File

@@ -672,10 +672,10 @@ func TestIsJSON(t *testing.T) {
func TestIsYAML(t *testing.T) { func TestIsYAML(t *testing.T) {
assert.True(t, IsYAML("hello:\n there:\n my-name: is quobix")) assert.True(t, IsYAML("hello:\n there:\n my-name: is quobix"))
assert.True(t, IsYAML("potato shoes")) assert.False(t, IsYAML("potato shoes"))
assert.False(t, IsYAML("{'hello':'there'}")) assert.False(t, IsYAML("{'hello':'there'}"))
assert.False(t, IsYAML("")) assert.False(t, IsYAML(""))
assert.False(t, IsYAML("8908: hello: yeah: \n12309812: :123")) assert.True(t, IsYAML("8908: hello: yeah: \n12309812: :123"))
} }
func TestConvertYAMLtoJSON(t *testing.T) { func TestConvertYAMLtoJSON(t *testing.T) {