Added more support for YAML merge nodes, anchors and aliases

And added deeper support for Aliases. Also added in local file handling through renamed `FSHandler` configuration property for the index.

Also re-ran `go fmt`

Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
Dave Shanley
2023-07-15 10:18:49 -04:00
committed by quobix
parent 3b7cbacc44
commit 25d8de9b0e
48 changed files with 925 additions and 531 deletions

View File

@@ -23,7 +23,7 @@ type Contact struct {
} }
// Build is not implemented for Contact (there is nothing to build). // Build is not implemented for Contact (there is nothing to build).
func (c *Contact) Build(root *yaml.Node, idx *index.SpecIndex) error { func (c *Contact) Build(_ *yaml.Node, _ *index.SpecIndex) error {
c.Reference = new(low.Reference) c.Reference = new(low.Reference)
// not implemented. // not implemented.
return nil return nil

View File

@@ -61,6 +61,8 @@ func (ex *Example) Hash() [32]byte {
// Build extracts extensions and example value // Build extracts extensions and example value
func (ex *Example) Build(root *yaml.Node, idx *index.SpecIndex) error { func (ex *Example) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
ex.Reference = new(low.Reference) ex.Reference = new(low.Reference)
ex.Extensions = low.ExtractExtensions(root) ex.Extensions = low.ExtractExtensions(root)
_, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content) _, ln, vn := utils.FindKeyNodeFull(ValueLabel, root.Content)

View File

@@ -20,7 +20,7 @@ x-cake: hot`
var idxNode yaml.Node var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode) mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateClosedAPIIndexConfig())
var n Example var n Example
err := low.BuildModel(idxNode.Content[0], &n) err := low.BuildModel(idxNode.Content[0], &n)
@@ -46,7 +46,7 @@ x-cake: hot`
var idxNode yaml.Node var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode) mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateClosedAPIIndexConfig())
var n Example var n Example
err := low.BuildModel(idxNode.Content[0], &n) err := low.BuildModel(idxNode.Content[0], &n)
@@ -73,7 +73,7 @@ value:
var idxNode yaml.Node var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode) mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateClosedAPIIndexConfig())
var n Example var n Example
err := low.BuildModel(idxNode.Content[0], &n) err := low.BuildModel(idxNode.Content[0], &n)
@@ -104,7 +104,39 @@ value:
var idxNode yaml.Node var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode) mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr) assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode) idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateClosedAPIIndexConfig())
var n Example
err := low.BuildModel(idxNode.Content[0], &n)
assert.NoError(t, err)
err = n.Build(idxNode.Content[0], idx)
assert.NoError(t, err)
assert.Equal(t, "hot", n.Summary.Value)
assert.Equal(t, "cakes", n.Description.Value)
if v, ok := n.Value.Value.([]interface{}); ok {
assert.Equal(t, "wow", v[0])
assert.Equal(t, "such array", v[1])
} else {
assert.Fail(t, "failed to decode correctly.")
}
}
func TestExample_Build_Success_MergeNode(t *testing.T) {
yml := `x-things: &things
summary: hot
description: cakes
value:
- wow
- such array
<<: *things`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndexWithConfig(&idxNode, index.CreateClosedAPIIndexConfig())
var n Example var n Example
err := low.BuildModel(idxNode.Content[0], &n) err := low.BuildModel(idxNode.Content[0], &n)

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -33,6 +34,8 @@ func (ex *ExternalDoc) FindExtension(ext string) *low.ValueReference[any] {
// Build will extract extensions from the ExternalDoc instance. // Build will extract extensions from the ExternalDoc instance.
func (ex *ExternalDoc) Build(root *yaml.Node, idx *index.SpecIndex) error { func (ex *ExternalDoc) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
ex.Reference = new(low.Reference) ex.Reference = new(low.Reference)
ex.Extensions = low.ExtractExtensions(root) ex.Extensions = low.ExtractExtensions(root)
return nil return nil

View File

@@ -6,6 +6,7 @@ package base
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/utils"
"sort" "sort"
"strings" "strings"
@@ -45,6 +46,8 @@ func (i *Info) GetExtensions() map[low.KeyReference[string]]low.ValueReference[a
// Build will extract out the Contact and Info objects from the supplied root node. // Build will extract out the Contact and Info objects from the supplied root node.
func (i *Info) Build(root *yaml.Node, idx *index.SpecIndex) error { func (i *Info) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
i.Reference = new(low.Reference) i.Reference = new(low.Reference)
i.Extensions = low.ExtractExtensions(root) i.Extensions = low.ExtractExtensions(root)

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings" "strings"
) )
@@ -25,6 +26,8 @@ type License struct {
// 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
func (l *License) Build(root *yaml.Node, idx *index.SpecIndex) error { func (l *License) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
l.Reference = new(low.Reference) l.Reference = new(low.Reference)
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

@@ -62,11 +62,11 @@ type Schema struct {
// Reference to the '$schema' dialect setting (3.1 only) // Reference to the '$schema' dialect setting (3.1 only)
SchemaTypeRef low.NodeReference[string] SchemaTypeRef low.NodeReference[string]
// In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean. // In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean.
ExclusiveMaximum low.NodeReference[*SchemaDynamicValue[bool, float64]] ExclusiveMaximum low.NodeReference[*SchemaDynamicValue[bool, float64]]
// In versions 2 and 3.0, this ExclusiveMinimum can only be a boolean. // In versions 2 and 3.0, this ExclusiveMinimum can only be a boolean.
ExclusiveMinimum low.NodeReference[*SchemaDynamicValue[bool, float64]] ExclusiveMinimum low.NodeReference[*SchemaDynamicValue[bool, float64]]
// In versions 2 and 3.0, this Type is a single value, so array will only ever have one value // In versions 2 and 3.0, this Type is a single value, so array will only ever have one value
// in version 3.1, Type can be multiple values // in version 3.1, Type can be multiple values
@@ -103,37 +103,37 @@ type Schema struct {
UnevaluatedProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, *bool]] UnevaluatedProperties low.NodeReference[*SchemaDynamicValue[*SchemaProxy, *bool]]
Anchor low.NodeReference[string] Anchor low.NodeReference[string]
// Compatible with all versions // Compatible with all versions
Title low.NodeReference[string] Title low.NodeReference[string]
MultipleOf low.NodeReference[float64] MultipleOf low.NodeReference[float64]
Maximum low.NodeReference[float64] Maximum low.NodeReference[float64]
Minimum low.NodeReference[float64] Minimum low.NodeReference[float64]
MaxLength low.NodeReference[int64] MaxLength low.NodeReference[int64]
MinLength low.NodeReference[int64] MinLength low.NodeReference[int64]
Pattern low.NodeReference[string] Pattern low.NodeReference[string]
Format low.NodeReference[string] Format low.NodeReference[string]
MaxItems low.NodeReference[int64] MaxItems low.NodeReference[int64]
MinItems low.NodeReference[int64] MinItems low.NodeReference[int64]
UniqueItems low.NodeReference[bool] UniqueItems low.NodeReference[bool]
MaxProperties low.NodeReference[int64] MaxProperties low.NodeReference[int64]
MinProperties low.NodeReference[int64] MinProperties low.NodeReference[int64]
Required low.NodeReference[[]low.ValueReference[string]] Required low.NodeReference[[]low.ValueReference[string]]
Enum low.NodeReference[[]low.ValueReference[any]] Enum low.NodeReference[[]low.ValueReference[any]]
Not low.NodeReference[*SchemaProxy] Not low.NodeReference[*SchemaProxy]
Properties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]] Properties low.NodeReference[map[low.KeyReference[string]]low.ValueReference[*SchemaProxy]]
AdditionalProperties low.NodeReference[any] AdditionalProperties low.NodeReference[any]
Description low.NodeReference[string] Description low.NodeReference[string]
ContentEncoding low.NodeReference[string] ContentEncoding low.NodeReference[string]
ContentMediaType low.NodeReference[string] ContentMediaType low.NodeReference[string]
Default low.NodeReference[any] Default low.NodeReference[any]
Nullable low.NodeReference[bool] Nullable low.NodeReference[bool]
ReadOnly low.NodeReference[bool] ReadOnly low.NodeReference[bool]
WriteOnly low.NodeReference[bool] WriteOnly low.NodeReference[bool]
XML low.NodeReference[*XML] XML low.NodeReference[*XML]
ExternalDocs low.NodeReference[*ExternalDoc] ExternalDocs low.NodeReference[*ExternalDoc]
Example low.NodeReference[any] Example low.NodeReference[any]
Deprecated low.NodeReference[bool] Deprecated low.NodeReference[bool]
Extensions map[low.KeyReference[string]]low.ValueReference[any] Extensions map[low.KeyReference[string]]low.ValueReference[any]
// Parent Proxy refers back to the low level SchemaProxy that is proxying this schema. // Parent Proxy refers back to the low level SchemaProxy that is proxying this schema.
ParentProxy *SchemaProxy ParentProxy *SchemaProxy
@@ -531,6 +531,8 @@ func (s *Schema) GetExtensions() map[low.KeyReference[string]]low.ValueReference
// - UnevaluatedProperties // - UnevaluatedProperties
// - Anchor // - Anchor
func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error { func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference) s.Reference = new(low.Reference)
if h, _, _ := utils.IsNodeRefValue(root); h { if h, _, _ := utils.IsNodeRefValue(root); h {
ref, err := low.LocateRefNode(root, idx) ref, err := low.LocateRefNode(root, idx)
@@ -581,47 +583,47 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
} }
// determine exclusive minimum type, bool (3.0) or int (3.1) // determine exclusive minimum type, bool (3.0) or int (3.1)
_, exMinLabel, exMinValue := utils.FindKeyNodeFullTop(ExclusiveMinimumLabel, root.Content) _, exMinLabel, exMinValue := utils.FindKeyNodeFullTop(ExclusiveMinimumLabel, root.Content)
if exMinValue != nil { if exMinValue != nil {
if utils.IsNodeBoolValue(exMinValue) { if utils.IsNodeBoolValue(exMinValue) {
val, _ := strconv.ParseBool(exMinValue.Value) val, _ := strconv.ParseBool(exMinValue.Value)
s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{ s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMinLabel, KeyNode: exMinLabel,
ValueNode: exMinValue, ValueNode: exMinValue,
Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val}, Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val},
} }
} }
if utils.IsNodeIntValue(exMinValue) { if utils.IsNodeIntValue(exMinValue) {
val, _ := strconv.ParseFloat(exMinValue.Value, 64) val, _ := strconv.ParseFloat(exMinValue.Value, 64)
s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{ s.ExclusiveMinimum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMinLabel, KeyNode: exMinLabel,
ValueNode: exMinValue, ValueNode: exMinValue,
Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val}, Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val},
} }
} }
} }
// determine exclusive maximum type, bool (3.0) or int (3.1) // determine exclusive maximum type, bool (3.0) or int (3.1)
_, exMaxLabel, exMaxValue := utils.FindKeyNodeFullTop(ExclusiveMaximumLabel, root.Content) _, exMaxLabel, exMaxValue := utils.FindKeyNodeFullTop(ExclusiveMaximumLabel, root.Content)
if exMaxValue != nil { if exMaxValue != nil {
if utils.IsNodeBoolValue(exMaxValue) { if utils.IsNodeBoolValue(exMaxValue) {
val, _ := strconv.ParseBool(exMaxValue.Value) val, _ := strconv.ParseBool(exMaxValue.Value)
s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{ s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMaxLabel, KeyNode: exMaxLabel,
ValueNode: exMaxValue, ValueNode: exMaxValue,
Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val}, Value: &SchemaDynamicValue[bool, float64]{N: 0, A: val},
} }
} }
if utils.IsNodeIntValue(exMaxValue) { if utils.IsNodeIntValue(exMaxValue) {
val, _ := strconv.ParseFloat(exMaxValue.Value, 64) val, _ := strconv.ParseFloat(exMaxValue.Value, 64)
s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{ s.ExclusiveMaximum = low.NodeReference[*SchemaDynamicValue[bool, float64]]{
KeyNode: exMaxLabel, KeyNode: exMaxLabel,
ValueNode: exMaxValue, ValueNode: exMaxValue,
Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val}, Value: &SchemaDynamicValue[bool, float64]{N: 1, B: val},
} }
} }
} }
// handle schema reference type if set. (3.1) // handle schema reference type if set. (3.1)
_, schemaRefLabel, schemaRefNode := utils.FindKeyNodeFullTop(SchemaTypeLabel, root.Content) _, schemaRefLabel, schemaRefNode := utils.FindKeyNodeFullTop(SchemaTypeLabel, root.Content)

View File

@@ -80,6 +80,7 @@ func (sp *SchemaProxy) Schema() *Schema {
return sp.rendered return sp.rendered
} }
schema := new(Schema) schema := new(Schema)
utils.CheckForMergeNodes(sp.vn)
err := schema.Build(sp.vn, sp.idx) err := schema.Build(sp.vn, sp.idx)
if err != nil { if err != nil {
sp.buildError = err sp.buildError = err

View File

@@ -73,3 +73,24 @@ func TestSchemaProxy_Build_HashInline(t *testing.T) {
assert.Equal(t, "6da88c34ba124c41f977db66a4fc5c1a951708d285c81bb0d47c3206f4c27ca8", assert.Equal(t, "6da88c34ba124c41f977db66a4fc5c1a951708d285c81bb0d47c3206f4c27ca8",
low.GenerateHashString(&sch)) low.GenerateHashString(&sch))
} }
func TestSchemaProxy_Build_UsingMergeNodes(t *testing.T) {
yml := `
x-common-definitions:
life_cycle_types: &life_cycle_types_def
type: string
enum: ["Onboarding", "Monitoring", "Re-Assessment"]
description: The type of life cycle
<<: *life_cycle_types_def`
var sch SchemaProxy
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
err := sch.Build(idxNode.Content[0], nil)
assert.NoError(t, err)
assert.Len(t, sch.Schema().Enum.Value, 3)
assert.Equal(t, "The type of life cycle", sch.Schema().Description.Value)
}

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -28,6 +29,8 @@ type SecurityRequirement struct {
// 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(root *yaml.Node, _ *index.SpecIndex) error { func (s *SecurityRequirement) Build(root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference) s.Reference = new(low.Reference)
var labelNode *yaml.Node var labelNode *yaml.Node
valueMap := make(map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]]) valueMap := make(map[low.KeyReference[string]]low.ValueReference[[]low.ValueReference[string]])

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -34,6 +35,8 @@ func (t *Tag) FindExtension(ext string) *low.ValueReference[any] {
// Build will extract extensions and external docs for the Tag. // Build will extract extensions and external docs for the Tag.
func (t *Tag) Build(root *yaml.Node, idx *index.SpecIndex) error { func (t *Tag) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
t.Reference = new(low.Reference) t.Reference = new(low.Reference)
t.Extensions = low.ExtractExtensions(root) t.Extensions = low.ExtractExtensions(root)

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -31,6 +32,8 @@ type XML struct {
// Build will extract extensions from the XML instance. // Build will extract extensions from the XML instance.
func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error { func (x *XML) Build(root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
x.Reference = new(low.Reference) x.Reference = new(low.Reference)
x.Extensions = low.ExtractExtensions(root) x.Extensions = low.ExtractExtensions(root)
return nil return nil

View File

@@ -6,14 +6,13 @@ package low
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"reflect"
"strconv"
"strings"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"reflect"
"strconv"
"strings"
) )
// FindItemInMap accepts a string key and a collection of KeyReference[string] and ValueReference[T]. Every // FindItemInMap accepts a string key and a collection of KeyReference[string] and ValueReference[T]. Every
@@ -86,14 +85,14 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
found[rv].Node.Column) found[rv].Node.Column)
} }
} }
return found[rv].Node, nil return utils.NodeAlias(found[rv].Node), nil
} }
} }
// perform a search for the reference in the index // perform a search for the reference in the index
foundRefs := idx.SearchIndexForReference(rv) foundRefs := idx.SearchIndexForReference(rv)
if len(foundRefs) > 0 { if len(foundRefs) > 0 {
return foundRefs[0].Node, nil return utils.NodeAlias(foundRefs[0].Node), nil
} }
// let's try something else to find our references. // let's try something else to find our references.
@@ -106,7 +105,7 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
nodes, fErr := path.Find(idx.GetRootNode()) nodes, fErr := path.Find(idx.GetRootNode())
if fErr == nil { if fErr == nil {
if len(nodes) > 0 { if len(nodes) > 0 {
return nodes[0], nil return utils.NodeAlias(nodes[0]), nil
} }
} }
} }
@@ -123,6 +122,7 @@ func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecInd
var circError error var circError error
var isReference bool var isReference bool
var referenceValue string var referenceValue string
root = utils.NodeAlias(root)
if h, _, rv := utils.IsNodeRefValue(root); h { if h, _, rv := utils.IsNodeRefValue(root); h {
ref, err := LocateRefNode(root, idx) ref, err := LocateRefNode(root, idx)
if ref != nil { if ref != nil {
@@ -167,6 +167,7 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in
var circError error var circError error
var isReference bool var isReference bool
var referenceValue string var referenceValue string
root = utils.NodeAlias(root)
if rf, rl, refVal := utils.IsNodeRefValue(root); rf { if rf, rl, refVal := utils.IsNodeRefValue(root); rf {
ref, err := LocateRefNode(root, idx) ref, err := LocateRefNode(root, idx)
if ref != nil { if ref != nil {
@@ -251,6 +252,7 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind
) { ) {
var ln, vn *yaml.Node var ln, vn *yaml.Node
var circError error var circError error
root = utils.NodeAlias(root)
if rf, rl, _ := utils.IsNodeRefValue(root); rf { if rf, rl, _ := utils.IsNodeRefValue(root); rf {
ref, err := LocateRefNode(root, idx) ref, err := LocateRefNode(root, idx)
if ref != nil { if ref != nil {
@@ -370,7 +372,10 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {
var currentKey *yaml.Node var currentKey *yaml.Node
skip := false skip := false
for i, node := range root.Content { rlen := len(root.Content)
for i := 0; i < rlen; i++ {
node := root.Content[i]
if !includeExtensions { if !includeExtensions {
if strings.HasPrefix(strings.ToLower(node.Value), "x-") { if strings.HasPrefix(strings.ToLower(node.Value), "x-") {
skip = true skip = true
@@ -386,6 +391,14 @@ func ExtractMapNoLookupExtensions[PT Buildable[N], N any](
continue continue
} }
if currentKey.Tag == "!!merge" && currentKey.Value == "<<" {
root.Content = append(root.Content, utils.NodeAlias(node).Content...)
rlen = len(root.Content)
currentKey = nil
continue
}
node = utils.NodeAlias(node)
var isReference bool var isReference bool
var referenceValue string var referenceValue string
// if value is a reference, we have to look it up in the index! // if value is a reference, we have to look it up in the index!
@@ -470,6 +483,7 @@ func ExtractMapExtensions[PT Buildable[N], N any](
var referenceValue string var referenceValue string
var labelNode, valueNode *yaml.Node var labelNode, valueNode *yaml.Node
var circError error var circError error
root = utils.NodeAlias(root)
if rf, rl, rv := utils.IsNodeRefValue(root); rf { if rf, rl, rv := utils.IsNodeRefValue(root); rf {
// locate reference in index. // locate reference in index.
ref, err := LocateRefNode(root, idx) ref, err := LocateRefNode(root, idx)
@@ -515,6 +529,7 @@ func ExtractMapExtensions[PT Buildable[N], N any](
buildMap := func(label *yaml.Node, value *yaml.Node, c chan mappingResult[PT], ec chan<- error, ref string) { buildMap := func(label *yaml.Node, value *yaml.Node, c chan mappingResult[PT], ec chan<- error, ref string) {
var n PT = new(N) var n PT = new(N)
value = utils.NodeAlias(value)
_ = BuildModel(value, n) _ = BuildModel(value, n)
err := n.Build(value, idx) err := n.Build(value, idx)
if err != nil { if err != nil {
@@ -544,6 +559,7 @@ func ExtractMapExtensions[PT Buildable[N], N any](
totalKeys := 0 totalKeys := 0
for i, en := range valueNode.Content { for i, en := range valueNode.Content {
en = utils.NodeAlias(en)
referenceValue = "" referenceValue = ""
if i%2 == 0 { if i%2 == 0 {
currentLabelNode = en currentLabelNode = en
@@ -620,6 +636,7 @@ func ExtractMap[PT Buildable[N], N any](
// //
// int64, float64, bool, string // int64, float64, bool, string
func ExtractExtensions(root *yaml.Node) map[KeyReference[string]]ValueReference[any] { func ExtractExtensions(root *yaml.Node) map[KeyReference[string]]ValueReference[any] {
root = utils.NodeAlias(root)
extensions := utils.FindExtensionNodes(root.Content) extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[KeyReference[string]]ValueReference[any]) extensionMap := make(map[KeyReference[string]]ValueReference[any])
for _, ext := range extensions { for _, ext := range extensions {

View File

@@ -23,6 +23,8 @@ func BuildModel(node *yaml.Node, model interface{}) error {
if node == nil { if node == nil {
return nil return nil
} }
node = utils.NodeAlias(node)
utils.CheckForMergeNodes(node)
if reflect.ValueOf(model).Type().Kind() != reflect.Pointer { if reflect.ValueOf(model).Type().Kind() != reflect.Pointer {
return fmt.Errorf("cannot build model on non-pointer: %v", reflect.ValueOf(model).Type().Kind()) return fmt.Errorf("cannot build model on non-pointer: %v", reflect.ValueOf(model).Type().Kind())
@@ -51,6 +53,7 @@ func BuildModel(node *yaml.Node, model interface{}) error {
kind := field.Kind() kind := field.Kind()
switch kind { switch kind {
case reflect.Struct, reflect.Slice, reflect.Map, reflect.Pointer: case reflect.Struct, reflect.Slice, reflect.Map, reflect.Pointer:
vn = utils.NodeAlias(vn)
err := SetField(&field, vn, kn) err := SetField(&field, vn, kn)
if err != nil { if err != nil {
return err return err
@@ -213,31 +216,31 @@ func SetField(field *reflect.Value, valueNode *yaml.Node, keyNode *yaml.Node) er
case reflect.TypeOf(NodeReference[float32]{}): case reflect.TypeOf(NodeReference[float32]{}):
if utils.IsNodeNumberValue(valueNode) { if utils.IsNodeNumberValue(valueNode) {
if field.CanSet() { if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 32) fv, _ := strconv.ParseFloat(valueNode.Value, 32)
nr := NodeReference[float32]{ nr := NodeReference[float32]{
Value: float32(fv), Value: float32(fv),
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
case reflect.TypeOf(NodeReference[float64]{}): case reflect.TypeOf(NodeReference[float64]{}):
if utils.IsNodeNumberValue(valueNode) { if utils.IsNodeNumberValue(valueNode) {
if field.CanSet() { if field.CanSet() {
fv, _ := strconv.ParseFloat(valueNode.Value, 64) fv, _ := strconv.ParseFloat(valueNode.Value, 64)
nr := NodeReference[float64]{ nr := NodeReference[float64]{
Value: fv, Value: fv,
ValueNode: valueNode, ValueNode: valueNode,
KeyNode: keyNode, KeyNode: keyNode,
} }
field.Set(reflect.ValueOf(nr)) field.Set(reflect.ValueOf(nr))
} }
} }
case reflect.TypeOf([]NodeReference[string]{}): case reflect.TypeOf([]NodeReference[string]{}):

View File

@@ -8,6 +8,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -71,6 +72,8 @@ func (s *SecurityDefinitions) FindSecurityDefinition(securityDef string) *low.Va
// Build will extract all definitions into SchemaProxy instances. // Build will extract all definitions into SchemaProxy instances.
func (d *Definitions) Build(root *yaml.Node, idx *index.SpecIndex) error { func (d *Definitions) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
errorChan := make(chan error) errorChan := make(chan error)
resultChan := make(chan definitionResult[*base.SchemaProxy]) resultChan := make(chan definitionResult[*base.SchemaProxy])
var defLabel *yaml.Node var defLabel *yaml.Node

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -27,6 +28,8 @@ func (e *Examples) FindExample(name string) *low.ValueReference[any] {
// Build will extract all examples and will attempt to unmarshal content into a map or slice based on type. // Build will extract all examples and will attempt to unmarshal content into a map or slice based on type.
func (e *Examples) Build(root *yaml.Node, _ *index.SpecIndex) error { func (e *Examples) Build(root *yaml.Node, _ *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
var keyNode, currNode *yaml.Node var keyNode, currNode *yaml.Node
var err error var err error
e.Values = make(map[low.KeyReference[string]]low.ValueReference[any]) e.Values = make(map[low.KeyReference[string]]low.ValueReference[any])

View File

@@ -52,6 +52,8 @@ func (h *Header) GetExtensions() map[low.KeyReference[string]]low.ValueReference
// Build will build out items, extensions and default value from the supplied node. // Build will build out items, extensions and default value from the supplied node.
func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error { func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
h.Extensions = low.ExtractExtensions(root) h.Extensions = low.ExtractExtensions(root)
items, err := low.ExtractObject[*Items](ItemsLabel, root, idx) items, err := low.ExtractObject[*Items](ItemsLabel, root, idx)
if err != nil { if err != nil {

View File

@@ -103,6 +103,8 @@ func (i *Items) Hash() [32]byte {
// Build will build out items and default value. // Build will build out items and default value.
func (i *Items) Build(root *yaml.Node, idx *index.SpecIndex) error { func (i *Items) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
i.Extensions = low.ExtractExtensions(root) i.Extensions = low.ExtractExtensions(root)
items, iErr := low.ExtractObject[*Items](ItemsLabel, root, idx) items, iErr := low.ExtractObject[*Items](ItemsLabel, root, idx)
if iErr != nil { if iErr != nil {

View File

@@ -9,6 +9,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -36,6 +37,8 @@ type Operation struct {
// Build will extract external docs, extensions, parameters, responses and security requirements. // Build will extract external docs, extensions, parameters, responses and security requirements.
func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error { func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)
// extract externalDocs // extract externalDocs

View File

@@ -95,6 +95,8 @@ func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
// Build will extract out extensions, schema, items and default value // Build will extract out extensions, schema, items and default value
func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error { func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
sch, sErr := base.ExtractSchema(root, idx) sch, sErr := base.ExtractSchema(root, idx)
if sErr != nil { if sErr != nil {

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -47,6 +48,8 @@ func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReferen
// Build will extract extensions, parameters and operations for all methods. Every method is handled // Build will extract extensions, parameters and operations for all methods. Every method is handled
// asynchronously, in order to keep things moving quickly for complex operations. // asynchronously, in order to keep things moving quickly for complex operations.
func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error { func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
skip := false skip := false
var currentNode *yaml.Node var currentNode *yaml.Node
@@ -120,7 +123,7 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
wg.Add(1) wg.Add(1)
go low.BuildModelAsync(pathNode, &op, &wg, &errors) low.BuildModelAsync(pathNode, &op, &wg, &errors)
opRef := low.NodeReference[*Operation]{ opRef := low.NodeReference[*Operation]{
Value: &op, Value: &op,

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -51,6 +52,8 @@ func (p *Paths) FindExtension(ext string) *low.ValueReference[any] {
// Build will extract extensions and paths from node. // Build will extract extensions and paths from node.
func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error { func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
skip := false skip := false
var currentNode *yaml.Node var currentNode *yaml.Node

View File

@@ -9,6 +9,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -43,6 +44,8 @@ func (r *Response) FindHeader(hType string) *low.ValueReference[*Header] {
// Build will extract schema, extensions, examples and headers from node // Build will extract schema, extensions, examples and headers from node
func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error { func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
s, err := base.ExtractSchema(root, idx) s, err := base.ExtractSchema(root, idx)
if err != nil { if err != nil {

View File

@@ -28,6 +28,8 @@ func (r *Responses) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
// Build will extract default value and extensions from node. // Build will extract default value and extensions from node.
func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error { func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {

View File

@@ -35,6 +35,8 @@ func (s *Scopes) FindScope(scope string) *low.ValueReference[string] {
// Build will extract scope values and extensions from node. // Build will extract scope values and extensions from node.
func (s *Scopes) Build(root *yaml.Node, idx *index.SpecIndex) error { func (s *Scopes) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
s.Extensions = low.ExtractExtensions(root) s.Extensions = low.ExtractExtensions(root)
valueMap := make(map[low.KeyReference[string]]low.ValueReference[string]) valueMap := make(map[low.KeyReference[string]]low.ValueReference[string])
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -38,6 +39,8 @@ func (ss *SecurityScheme) GetExtensions() map[low.KeyReference[string]]low.Value
// Build will extract extensions and scopes from the node. // Build will extract extensions and scopes from the node.
func (ss *SecurityScheme) Build(root *yaml.Node, idx *index.SpecIndex) error { func (ss *SecurityScheme) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
ss.Extensions = low.ExtractExtensions(root) ss.Extensions = low.ExtractExtensions(root)
scopes, sErr := low.ExtractObject[*Scopes](ScopesLabel, root, idx) scopes, sErr := low.ExtractObject[*Scopes](ScopesLabel, root, idx)

View File

@@ -6,6 +6,7 @@ package v3
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"github.com/pb33f/libopenapi/utils"
"sort" "sort"
"strings" "strings"
@@ -39,6 +40,8 @@ func (cb *Callback) FindExpression(exp string) *low.ValueReference[*PathItem] {
// Build will extract extensions, expressions and PathItem objects for Callback // Build will extract extensions, expressions and PathItem objects for Callback
func (cb *Callback) Build(root *yaml.Node, idx *index.SpecIndex) error { func (cb *Callback) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
cb.Reference = new(low.Reference) cb.Reference = new(low.Reference)
cb.Extensions = low.ExtractExtensions(root) cb.Extensions = low.ExtractExtensions(root)

View File

@@ -127,6 +127,8 @@ func (co *Components) FindCallback(callback string) *low.ValueReference[*Callbac
} }
func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error { func (co *Components) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
co.Reference = new(low.Reference) co.Reference = new(low.Reference)
co.Extensions = low.ExtractExtensions(root) co.Extensions = low.ExtractExtensions(root)

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings" "strings"
) )
@@ -58,6 +59,8 @@ func (en *Encoding) Hash() [32]byte {
// Build will extract all Header objects from supplied node. // Build will extract all Header objects from supplied node.
func (en *Encoding) Build(root *yaml.Node, idx *index.SpecIndex) error { func (en *Encoding) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
en.Reference = new(low.Reference) en.Reference = new(low.Reference)
headers, hL, hN, err := low.ExtractMap[*Header](HeadersLabel, root, idx) headers, hL, hN, err := low.ExtractMap[*Header](HeadersLabel, root, idx)
if err != nil { if err != nil {

View File

@@ -96,6 +96,8 @@ func (h *Header) Hash() [32]byte {
// Build will extract extensions, examples, schema and content/media types from node. // Build will extract extensions, examples, schema and content/media types from node.
func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error { func (h *Header) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
h.Reference = new(low.Reference) h.Reference = new(low.Reference)
h.Extensions = low.ExtractExtensions(root) h.Extensions = low.ExtractExtensions(root)

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -53,6 +54,8 @@ func (l *Link) FindExtension(ext string) *low.ValueReference[any] {
// Build will extract extensions and servers from the node. // Build will extract extensions and servers from the node.
func (l *Link) Build(root *yaml.Node, idx *index.SpecIndex) error { func (l *Link) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
l.Reference = new(low.Reference) l.Reference = new(low.Reference)
l.Extensions = low.ExtractExtensions(root) l.Extensions = low.ExtractExtensions(root)
// extract server. // extract server.

View File

@@ -55,6 +55,8 @@ func (mt *MediaType) GetAllExamples() map[low.KeyReference[string]]low.ValueRefe
// Build will extract examples, extensions, schema and encoding from node. // Build will extract examples, extensions, schema and encoding from node.
func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error { func (mt *MediaType) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
mt.Reference = new(low.Reference) mt.Reference = new(low.Reference)
mt.Extensions = low.ExtractExtensions(root) mt.Extensions = low.ExtractExtensions(root)

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -36,6 +37,8 @@ func (o *OAuthFlows) FindExtension(ext string) *low.ValueReference[any] {
// Build will extract extensions and all OAuthFlow types from the supplied node. // Build will extract extensions and all OAuthFlow types from the supplied node.
func (o *OAuthFlows) Build(root *yaml.Node, idx *index.SpecIndex) error { func (o *OAuthFlows) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
o.Reference = new(low.Reference) o.Reference = new(low.Reference)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)

View File

@@ -9,6 +9,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -55,6 +56,8 @@ func (o *Operation) FindSecurityRequirement(name string) []low.ValueReference[st
// Build will extract external docs, parameters, request body, responses, callbacks, security and servers. // Build will extract external docs, parameters, request body, responses, callbacks, security and servers.
func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error { func (o *Operation) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
o.Reference = new(low.Reference) o.Reference = new(low.Reference)
o.Extensions = low.ExtractExtensions(root) o.Extensions = low.ExtractExtensions(root)

View File

@@ -59,6 +59,8 @@ func (p *Parameter) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
// Build will extract examples, extensions and content/media types. // Build will extract examples, extensions and content/media types.
func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error { func (p *Parameter) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
p.Reference = new(low.Reference) p.Reference = new(low.Reference)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)

View File

@@ -109,6 +109,8 @@ func (p *PathItem) GetExtensions() map[low.KeyReference[string]]low.ValueReferen
// Build extracts extensions, parameters, servers and each http method defined. // Build extracts extensions, parameters, servers and each http method defined.
// everything is extracted asynchronously for speed. // everything is extracted asynchronously for speed.
func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error { func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
p.Reference = new(low.Reference) p.Reference = new(low.Reference)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
skip := false skip := false
@@ -232,7 +234,7 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
} }
} }
wg.Add(1) wg.Add(1)
go low.BuildModelAsync(pathNode, &op, &wg, &errors) low.BuildModelAsync(pathNode, &op, &wg, &errors)
opRef := low.NodeReference[*Operation]{ opRef := low.NodeReference[*Operation]{
Value: &op, Value: &op,

View File

@@ -59,6 +59,8 @@ func (p *Paths) GetExtensions() map[low.KeyReference[string]]low.ValueReference[
// Build will extract extensions and all PathItems. This happens asynchronously for speed. // Build will extract extensions and all PathItems. This happens asynchronously for speed.
func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error { func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
p.Reference = new(low.Reference) p.Reference = new(low.Reference)
p.Extensions = low.ExtractExtensions(root) p.Extensions = low.ExtractExtensions(root)
skip := false skip := false

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -40,6 +41,8 @@ func (rb *RequestBody) FindContent(cType string) *low.ValueReference[*MediaType]
// Build will extract extensions and MediaType objects from the node. // Build will extract extensions and MediaType objects from the node.
func (rb *RequestBody) Build(root *yaml.Node, idx *index.SpecIndex) error { func (rb *RequestBody) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
rb.Reference = new(low.Reference) rb.Reference = new(low.Reference)
rb.Extensions = low.ExtractExtensions(root) rb.Extensions = low.ExtractExtensions(root)

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -54,6 +55,8 @@ func (r *Response) FindLink(hType string) *low.ValueReference[*Link] {
// Build will extract headers, extensions, content and links from node. // Build will extract headers, extensions, content and links from node.
func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error { func (r *Response) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
r.Reference = new(low.Reference) r.Reference = new(low.Reference)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)

View File

@@ -46,8 +46,10 @@ func (r *Responses) GetExtensions() map[low.KeyReference[string]]low.ValueRefere
// Build will extract default response and all Response objects for each code // Build will extract default response and all Response objects for each code
func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error { func (r *Responses) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
r.Reference = new(low.Reference) r.Reference = new(low.Reference)
r.Extensions = low.ExtractExtensions(root) r.Extensions = low.ExtractExtensions(root)
utils.CheckForMergeNodes(root)
if utils.IsNodeMap(root) { if utils.IsNodeMap(root) {
codes, err := low.ExtractMapNoLookup[*Response](root, idx) codes, err := low.ExtractMapNoLookup[*Response](root, idx)

View File

@@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"sort" "sort"
"strings" "strings"
@@ -48,6 +49,8 @@ func (ss *SecurityScheme) GetExtensions() map[low.KeyReference[string]]low.Value
// Build will extract OAuthFlows and extensions from the node. // Build will extract OAuthFlows and extensions from the node.
func (ss *SecurityScheme) Build(root *yaml.Node, idx *index.SpecIndex) error { func (ss *SecurityScheme) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
ss.Reference = new(low.Reference) ss.Reference = new(low.Reference)
ss.Extensions = low.ExtractExtensions(root) ss.Extensions = low.ExtractExtensions(root)

View File

@@ -35,6 +35,8 @@ 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(root *yaml.Node, idx *index.SpecIndex) error { func (s *Server) Build(root *yaml.Node, idx *index.SpecIndex) error {
root = utils.NodeAlias(root)
utils.CheckForMergeNodes(root)
s.Reference = new(low.Reference) s.Reference = new(low.Reference)
s.Extensions = low.ExtractExtensions(root) s.Extensions = low.ExtractExtensions(root)
kn, vars := utils.FindKeyNode(VariablesLabel, root.Content) kn, vars := utils.FindKeyNode(VariablesLabel, root.Content)

View File

@@ -505,33 +505,34 @@ paths:
assert.Equal(t, d, strings.TrimSpace(string(rend))) assert.Equal(t, d, strings.TrimSpace(string(rend)))
} }
func TestDocument_RemoteWithoutBaseURL(t *testing.T) { // disabled for now as the host is timing out
//func TestDocument_RemoteWithoutBaseURL(t *testing.T) {
// This test will push the index to do try and locate remote references that use relative references //
spec := `openapi: 3.0.2 // // This test will push the index to do try and locate remote references that use relative references
info: // spec := `openapi: 3.0.2
title: Test //info:
version: 1.0.0 // title: Test
paths: // version: 1.0.0
/test: //paths:
get: // /test:
parameters: // get:
- $ref: "https://schemas.opengis.net/ogcapi/features/part2/1.0/openapi/ogcapi-features-2.yaml#/components/parameters/crs"` // parameters:
// - $ref: "https://schemas.opengis.net/ogcapi/features/part2/1.0/openapi/ogcapi-features-2.yaml#/components/parameters/crs"`
config := datamodel.NewOpenDocumentConfiguration() //
// config := datamodel.NewOpenDocumentConfiguration()
doc, err := NewDocumentWithConfiguration([]byte(spec), config) //
if err != nil { // doc, err := NewDocumentWithConfiguration([]byte(spec), config)
panic(err) // if err != nil {
} // panic(err)
// }
result, errs := doc.BuildV3Model() //
if len(errs) > 0 { // result, errs := doc.BuildV3Model()
panic(errs) // if len(errs) > 0 {
} // panic(errs)
// }
assert.Equal(t, "crs", result.Model.Paths.PathItems["/test"].Get.Parameters[0].Name) //
} // assert.Equal(t, "crs", result.Model.Paths.PathItems["/test"].Get.Parameters[0].Name)
//}
func TestDocument_ExampleMap(t *testing.T) { func TestDocument_ExampleMap(t *testing.T) {
var d = `openapi: "3.1" var d = `openapi: "3.1"

View File

@@ -4,86 +4,86 @@
package index package index
import ( import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"github.com/pb33f/libopenapi/utils" "github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// FindComponent will locate a component by its reference, returns nil if nothing is found. // FindComponent will locate a component by its reference, returns nil if nothing is found.
// This method will recurse through remote, local and file references. For each new external reference // This method will recurse through remote, local and file references. For each new external reference
// a new index will be created. These indexes can then be traversed recursively. // a new index will be created. These indexes can then be traversed recursively.
func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Reference { func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Reference {
if index.root == nil { if index.root == nil {
return nil return nil
} }
remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) { remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) {
if index.config.AllowRemoteLookup { if index.config.AllowRemoteLookup {
return index.lookupRemoteReference(id) return index.lookupRemoteReference(id)
} else { } else {
return nil, nil, fmt.Errorf("remote lookups are not permitted, " + return nil, nil, fmt.Errorf("remote lookups are not permitted, " +
"please set AllowRemoteLookup to true in the configuration") "please set AllowRemoteLookup to true in the configuration")
} }
} }
fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) { fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) {
if index.config.AllowFileLookup { if index.config.AllowFileLookup {
return index.lookupFileReference(id) return index.lookupFileReference(id)
} else { } else {
return nil, nil, fmt.Errorf("local lookups are not permitted, " + return nil, nil, fmt.Errorf("local lookups are not permitted, " +
"please set AllowFileLookup to true in the configuration") "please set AllowFileLookup to true in the configuration")
} }
} }
switch DetermineReferenceResolveType(componentId) { switch DetermineReferenceResolveType(componentId) {
case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case. case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case.
return index.FindComponentInRoot(componentId) return index.FindComponentInRoot(componentId)
case HttpResolve: case HttpResolve:
uri := strings.Split(componentId, "#") uri := strings.Split(componentId, "#")
if len(uri) >= 2 { if len(uri) >= 2 {
return index.performExternalLookup(uri, componentId, remoteLookup, parent) return index.performExternalLookup(uri, componentId, remoteLookup, parent)
} }
if len(uri) == 1 { if len(uri) == 1 {
// if there is no reference, second segment is empty / has no name // if there is no reference, second segment is empty / has no name
// this means there is no component to look-up and the entire file should be pulled in. // this means there is no component to look-up and the entire file should be pulled in.
// to stop all the other code from breaking (that is expecting a component), let's just post-pend // to stop all the other code from breaking (that is expecting a component), let's just post-pend
// a hash to the end of the componentId and ensure the uri slice is as expected. // a hash to the end of the componentId and ensure the uri slice is as expected.
// described in https://github.com/pb33f/libopenapi/issues/37 // described in https://github.com/pb33f/libopenapi/issues/37
componentId = fmt.Sprintf("%s#", componentId) componentId = fmt.Sprintf("%s#", componentId)
uri = append(uri, "") uri = append(uri, "")
return index.performExternalLookup(uri, componentId, remoteLookup, parent) return index.performExternalLookup(uri, componentId, remoteLookup, parent)
} }
case FileResolve: case FileResolve:
uri := strings.Split(componentId, "#") uri := strings.Split(componentId, "#")
if len(uri) == 2 { if len(uri) == 2 {
return index.performExternalLookup(uri, componentId, fileLookup, parent) return index.performExternalLookup(uri, componentId, fileLookup, parent)
} }
if len(uri) == 1 { if len(uri) == 1 {
// if there is no reference, second segment is empty / has no name // if there is no reference, second segment is empty / has no name
// this means there is no component to look-up and the entire file should be pulled in. // this means there is no component to look-up and the entire file should be pulled in.
// to stop all the other code from breaking (that is expecting a component), let's just post-pend // to stop all the other code from breaking (that is expecting a component), let's just post-pend
// a hash to the end of the componentId and ensure the uri slice is as expected. // a hash to the end of the componentId and ensure the uri slice is as expected.
// described in https://github.com/pb33f/libopenapi/issues/37 // described in https://github.com/pb33f/libopenapi/issues/37
// //
// ^^ this same issue was re-reported in file based lookups in vacuum. // ^^ this same issue was re-reported in file based lookups in vacuum.
// more info here: https://github.com/daveshanley/vacuum/issues/225 // more info here: https://github.com/daveshanley/vacuum/issues/225
componentId = fmt.Sprintf("%s#", componentId) componentId = fmt.Sprintf("%s#", componentId)
uri = append(uri, "") uri = append(uri, "")
return index.performExternalLookup(uri, componentId, fileLookup, parent) return index.performExternalLookup(uri, componentId, fileLookup, parent)
} }
} }
return nil return nil
} }
var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second} var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second}
@@ -91,339 +91,357 @@ var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second}
type RemoteURLHandler = func(url string) (*http.Response, error) type RemoteURLHandler = func(url string) (*http.Response, error)
func getRemoteDoc(g RemoteURLHandler, u string, d chan []byte, e chan error) { func getRemoteDoc(g RemoteURLHandler, u string, d chan []byte, e chan error) {
resp, err := g(u) resp, err := g(u)
if err != nil { if err != nil {
e <- err e <- err
close(e) close(e)
close(d) close(d)
return return
} }
var body []byte var body []byte
body, _ = io.ReadAll(resp.Body) body, _ = io.ReadAll(resp.Body)
d <- body d <- body
close(e) close(e)
close(d) close(d)
} }
func (index *SpecIndex) lookupRemoteReference(ref string) (*yaml.Node, *yaml.Node, error) { func (index *SpecIndex) lookupRemoteReference(ref string) (*yaml.Node, *yaml.Node, error) {
// split string to remove file reference // split string to remove file reference
uri := strings.Split(ref, "#") uri := strings.Split(ref, "#")
// have we already seen this remote source? // have we already seen this remote source?
var parsedRemoteDocument *yaml.Node var parsedRemoteDocument *yaml.Node
alreadySeen, foundDocument := index.CheckForSeenRemoteSource(uri[0]) alreadySeen, foundDocument := index.CheckForSeenRemoteSource(uri[0])
if alreadySeen { if alreadySeen {
parsedRemoteDocument = foundDocument parsedRemoteDocument = foundDocument
} else { } else {
d := make(chan bool) d := make(chan bool)
var body []byte var body []byte
var err error var err error
go func(uri string) { go func(uri string) {
bc := make(chan []byte) bc := make(chan []byte)
ec := make(chan error) ec := make(chan error)
var getter RemoteURLHandler = httpClient.Get var getter RemoteURLHandler = httpClient.Get
if index.config != nil && index.config.RemoteURLHandler != nil { if index.config != nil && index.config.RemoteURLHandler != nil {
getter = index.config.RemoteURLHandler getter = index.config.RemoteURLHandler
} }
// if we have a remote handler, use it instead of the default. // if we have a remote handler, use it instead of the default.
if index.config != nil && index.config.RemoteHandler != nil { if index.config != nil && index.config.FSHandler != nil {
go func() { go func() {
remoteFS := index.config.RemoteHandler remoteFS := index.config.FSHandler
remoteFile, rErr := remoteFS.Open(uri) remoteFile, rErr := remoteFS.Open(uri)
if rErr != nil { if rErr != nil {
e := fmt.Errorf("unable to open remote file: %s", rErr) e := fmt.Errorf("unable to open remote file: %s", rErr)
ec <- e ec <- e
return return
} }
b, ioErr := io.ReadAll(remoteFile) b, ioErr := io.ReadAll(remoteFile)
if ioErr != nil { if ioErr != nil {
e := fmt.Errorf("unable to read remote file bytes: %s", ioErr) e := fmt.Errorf("unable to read remote file bytes: %s", ioErr)
ec <- e ec <- e
return return
} }
bc <- b bc <- b
}() }()
} else { } else {
go getRemoteDoc(getter, uri, bc, ec) go getRemoteDoc(getter, uri, bc, ec)
} }
select { select {
case v := <-bc: case v := <-bc:
body = v body = v
break break
case er := <-ec: case er := <-ec:
err = er err = er
break break
} }
if len(body) > 0 { if len(body) > 0 {
var remoteDoc yaml.Node var remoteDoc yaml.Node
er := yaml.Unmarshal(body, &remoteDoc) er := yaml.Unmarshal(body, &remoteDoc)
if er != nil { if er != nil {
err = er err = er
d <- true d <- true
return return
} }
parsedRemoteDocument = &remoteDoc parsedRemoteDocument = &remoteDoc
if index.config != nil { if index.config != nil {
index.config.seenRemoteSources.Store(uri, &remoteDoc) index.config.seenRemoteSources.Store(uri, &remoteDoc)
} }
} }
d <- true d <- true
}(uri[0]) }(uri[0])
// wait for double go fun. // wait for double go fun.
<-d <-d
if err != nil { if err != nil {
// no bueno. // no bueno.
return nil, nil, err return nil, nil, err
} }
} }
// lookup item from reference by using a path query. // lookup item from reference by using a path query.
var query string var query string
if len(uri) >= 2 { if len(uri) >= 2 {
query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", "."))
} else { } else {
query = "$" query = "$"
} }
// remove any URL encoding // remove any URL encoding
query = strings.Replace(query, "~1", "./", 1) query = strings.Replace(query, "~1", "./", 1)
query = strings.ReplaceAll(query, "~1", "/") query = strings.ReplaceAll(query, "~1", "/")
path, err := yamlpath.NewPath(query) path, err := yamlpath.NewPath(query)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
result, _ := path.Find(parsedRemoteDocument) result, _ := path.Find(parsedRemoteDocument)
if len(result) == 1 { if len(result) == 1 {
return result[0], parsedRemoteDocument, nil return result[0], parsedRemoteDocument, nil
} }
return nil, nil, nil return nil, nil, nil
} }
func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node, error) { func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node, error) {
// split string to remove file reference // split string to remove file reference
uri := strings.Split(ref, "#") uri := strings.Split(ref, "#")
file := strings.ReplaceAll(uri[0], "file:", "") file := strings.ReplaceAll(uri[0], "file:", "")
filePath := filepath.Dir(file) filePath := filepath.Dir(file)
fileName := filepath.Base(file) fileName := filepath.Base(file)
var parsedRemoteDocument *yaml.Node var parsedRemoteDocument *yaml.Node
if index.seenRemoteSources[file] != nil { if index.seenRemoteSources[file] != nil {
parsedRemoteDocument = index.seenRemoteSources[file] parsedRemoteDocument = index.seenRemoteSources[file]
} else { } else {
base := index.config.BasePath base := index.config.BasePath
fileToRead := filepath.Join(base, filePath, fileName) fileToRead := filepath.Join(base, filePath, fileName)
var body []byte
var err error
// try and read the file off the local file system, if it fails // if we have an FS handler, use it instead of the default behavior
// check for a baseURL and then ask our remote lookup function to go try and get it. if index.config != nil && index.config.FSHandler != nil {
body, err := os.ReadFile(fileToRead) remoteFS := index.config.FSHandler
remoteFile, rErr := remoteFS.Open(fileToRead)
if rErr != nil {
e := fmt.Errorf("unable to open file: %s", rErr)
return nil, nil, e
}
body, err = io.ReadAll(remoteFile)
if err != nil {
e := fmt.Errorf("unable to read file bytes: %s", err)
return nil, nil, e
}
if err != nil { } else {
// if we have a baseURL, then we can try and get the file from there. // try and read the file off the local file system, if it fails
if index.config != nil && index.config.BaseURL != nil { // check for a baseURL and then ask our remote lookup function to go try and get it.
body, err = os.ReadFile(fileToRead)
u := index.config.BaseURL if err != nil {
remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true)
a, b, e := index.lookupRemoteReference(remoteRef)
if e != nil {
// give up, we can't find the file, not locally, not remotely. It's toast.
return nil, nil, e
}
return a, b, nil
} else { // if we have a baseURL, then we can try and get the file from there.
// no baseURL? then we can't do anything, give up. if index.config != nil && index.config.BaseURL != nil {
return nil, nil, err
}
}
var remoteDoc yaml.Node u := index.config.BaseURL
err = yaml.Unmarshal(body, &remoteDoc) remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true)
if err != nil { a, b, e := index.lookupRemoteReference(remoteRef)
return nil, nil, err if e != nil {
} // give up, we can't find the file, not locally, not remotely. It's toast.
parsedRemoteDocument = &remoteDoc return nil, nil, e
if index.seenLocalSources != nil { }
index.sourceLock.Lock() return a, b, nil
index.seenLocalSources[file] = &remoteDoc
index.sourceLock.Unlock()
}
}
// lookup item from reference by using a path query. } else {
var query string // no baseURL? then we can't do anything, give up.
if len(uri) >= 2 { return nil, nil, err
query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) }
} else { }
query = "$" }
} var remoteDoc yaml.Node
err = yaml.Unmarshal(body, &remoteDoc)
if err != nil {
return nil, nil, err
}
parsedRemoteDocument = &remoteDoc
if index.seenLocalSources != nil {
index.sourceLock.Lock()
index.seenLocalSources[file] = &remoteDoc
index.sourceLock.Unlock()
}
}
// remove any URL encoding // lookup item from reference by using a path query.
query = strings.Replace(query, "~1", "./", 1) var query string
query = strings.ReplaceAll(query, "~1", "/") if len(uri) >= 2 {
query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", "."))
} else {
query = "$"
}
path, err := yamlpath.NewPath(query) // remove any URL encoding
if err != nil { query = strings.Replace(query, "~1", "./", 1)
return nil, nil, err query = strings.ReplaceAll(query, "~1", "/")
}
result, _ := path.Find(parsedRemoteDocument)
if len(result) == 1 {
return result[0], parsedRemoteDocument, nil
}
return nil, parsedRemoteDocument, nil path, err := yamlpath.NewPath(query)
if err != nil {
return nil, nil, err
}
result, _ := path.Find(parsedRemoteDocument)
if len(result) == 1 {
return result[0], parsedRemoteDocument, nil
}
return nil, parsedRemoteDocument, nil
} }
func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference { func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference {
if index.root != nil { if index.root != nil {
// check component for url encoding. // check component for url encoding.
if strings.Contains(componentId, "%") { if strings.Contains(componentId, "%") {
// decode the url. // decode the url.
componentId, _ = url.QueryUnescape(componentId) componentId, _ = url.QueryUnescape(componentId)
} }
name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId)
path, err := yamlpath.NewPath(friendlySearch) path, err := yamlpath.NewPath(friendlySearch)
if path == nil || err != nil { if path == nil || err != nil {
return nil // no component found return nil // no component found
} }
res, _ := path.Find(index.root) res, _ := path.Find(index.root)
if len(res) == 1 { if len(res) == 1 {
resNode := res[0] resNode := res[0]
if res[0].Kind == yaml.DocumentNode { if res[0].Kind == yaml.DocumentNode {
resNode = res[0].Content[0] resNode = res[0].Content[0]
} }
ref := &Reference{ ref := &Reference{
Definition: componentId, Definition: componentId,
Name: name, Name: name,
Node: resNode, Node: resNode,
Path: friendlySearch, Path: friendlySearch,
RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}), RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}),
} }
return ref return ref
} }
} }
return nil return nil
} }
func (index *SpecIndex) performExternalLookup(uri []string, componentId string, func (index *SpecIndex) performExternalLookup(uri []string, componentId string,
lookupFunction ExternalLookupFunction, parent *yaml.Node) *Reference { lookupFunction ExternalLookupFunction, parent *yaml.Node) *Reference {
if len(uri) > 0 { if len(uri) > 0 {
index.externalLock.RLock() index.externalLock.RLock()
externalSpecIndex := index.externalSpecIndex[uri[0]] externalSpecIndex := index.externalSpecIndex[uri[0]]
index.externalLock.RUnlock() index.externalLock.RUnlock()
if externalSpecIndex == nil { if externalSpecIndex == nil {
_, newRoot, err := lookupFunction(componentId) _, newRoot, err := lookupFunction(componentId)
if err != nil { if err != nil {
indexError := &IndexingError{ indexError := &IndexingError{
Err: err, Err: err,
Node: parent, Node: parent,
Path: componentId, Path: componentId,
} }
index.errorLock.Lock() index.errorLock.Lock()
index.refErrors = append(index.refErrors, indexError) index.refErrors = append(index.refErrors, indexError)
index.errorLock.Unlock() index.errorLock.Unlock()
return nil return nil
} }
// cool, cool, lets index this spec also. This is a recursive action and will keep going // cool, cool, lets index this spec also. This is a recursive action and will keep going
// until all remote references have been found. // until all remote references have been found.
var bp *url.URL var bp *url.URL
var bd string var bd string
if index.config.BaseURL != nil { if index.config.BaseURL != nil {
bp = index.config.BaseURL bp = index.config.BaseURL
} }
if index.config.BasePath != "" { if index.config.BasePath != "" {
bd = index.config.BasePath bd = index.config.BasePath
} }
var path, newBasePath string var path, newBasePath string
var newUrl *url.URL var newUrl *url.URL
if bp != nil { if bp != nil {
path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false) path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false)
newUrl, _ = url.Parse(path) newUrl, _ = url.Parse(path)
newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path))) newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path)))
} }
if bd != "" { if bd != "" {
if len(uri[0]) > 0 { if len(uri[0]) > 0 {
// if there is no base url defined, but we can know we have been requested remotely, // if there is no base url defined, but we can know we have been requested remotely,
// set the base url to the remote url base path. // set the base url to the remote url base path.
// first check if the first param is actually a URL // first check if the first param is actually a URL
io, er := url.ParseRequestURI(uri[0]) io, er := url.ParseRequestURI(uri[0])
if er != nil { if er != nil {
newBasePath = filepath.Dir(filepath.Join(bd, uri[0])) newBasePath = filepath.Dir(filepath.Join(bd, uri[0]))
} else { } else {
if newUrl == nil || newUrl.String() != io.String() { if newUrl == nil || newUrl.String() != io.String() {
newUrl, _ = url.Parse(fmt.Sprintf("%s://%s%s", io.Scheme, io.Host, filepath.Dir(io.Path))) newUrl, _ = url.Parse(fmt.Sprintf("%s://%s%s", io.Scheme, io.Host, filepath.Dir(io.Path)))
} }
newBasePath = filepath.Dir(filepath.Join(bd, uri[1])) newBasePath = filepath.Dir(filepath.Join(bd, uri[1]))
} }
} else { } else {
newBasePath = filepath.Dir(filepath.Join(bd, uri[0])) newBasePath = filepath.Dir(filepath.Join(bd, uri[0]))
} }
} }
if newUrl != nil || newBasePath != "" { if newUrl != nil || newBasePath != "" {
newConfig := &SpecIndexConfig{ newConfig := &SpecIndexConfig{
BaseURL: newUrl, BaseURL: newUrl,
BasePath: newBasePath, BasePath: newBasePath,
AllowRemoteLookup: index.config.AllowRemoteLookup, AllowRemoteLookup: index.config.AllowRemoteLookup,
AllowFileLookup: index.config.AllowFileLookup, AllowFileLookup: index.config.AllowFileLookup,
ParentIndex: index, ParentIndex: index,
seenRemoteSources: index.config.seenRemoteSources, seenRemoteSources: index.config.seenRemoteSources,
remoteLock: index.config.remoteLock, remoteLock: index.config.remoteLock,
uri: uri, uri: uri,
} }
var newIndex *SpecIndex var newIndex *SpecIndex
seen := index.SearchAncestryForSeenURI(uri[0]) seen := index.SearchAncestryForSeenURI(uri[0])
if seen == nil { if seen == nil {
newIndex = NewSpecIndexWithConfig(newRoot, newConfig) newIndex = NewSpecIndexWithConfig(newRoot, newConfig)
index.refLock.Lock() index.refLock.Lock()
index.externalLock.Lock() index.externalLock.Lock()
index.externalSpecIndex[uri[0]] = newIndex index.externalSpecIndex[uri[0]] = newIndex
index.externalLock.Unlock() index.externalLock.Unlock()
newIndex.relativePath = path newIndex.relativePath = path
newIndex.parentIndex = index newIndex.parentIndex = index
index.AddChild(newIndex) index.AddChild(newIndex)
index.refLock.Unlock() index.refLock.Unlock()
externalSpecIndex = newIndex externalSpecIndex = newIndex
} else { } else {
externalSpecIndex = seen externalSpecIndex = seen
} }
} }
} }
if externalSpecIndex != nil { if externalSpecIndex != nil {
foundRef := externalSpecIndex.FindComponentInRoot(uri[1]) foundRef := externalSpecIndex.FindComponentInRoot(uri[1])
if foundRef != nil { if foundRef != nil {
nameSegs := strings.Split(uri[1], "/") nameSegs := strings.Split(uri[1], "/")
ref := &Reference{ ref := &Reference{
Definition: componentId, Definition: componentId,
Name: nameSegs[len(nameSegs)-1], Name: nameSegs[len(nameSegs)-1],
Node: foundRef.Node, Node: foundRef.Node,
IsRemote: true, IsRemote: true,
RemoteLocation: componentId, RemoteLocation: componentId,
Path: foundRef.Path, Path: foundRef.Path,
} }
return ref return ref
} }
} }
} }
return nil return nil
} }

View File

@@ -288,7 +288,7 @@ paths:
_ = yaml.Unmarshal([]byte(spec), &rootNode) _ = yaml.Unmarshal([]byte(spec), &rootNode)
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
c.RemoteHandler = FS{} c.FSHandler = FS{}
index := NewSpecIndexWithConfig(&rootNode, c) index := NewSpecIndexWithConfig(&rootNode, c)
@@ -301,6 +301,35 @@ paths:
assert.Equal(t, "query", crsParam.Node.Content[5].Value) assert.Equal(t, "query", crsParam.Node.Content[5].Value)
} }
func TestSpecIndex_UseFileHandler(t *testing.T) {
spec := `openapi: 3.1.0
info:
title: Test Remote Handler
version: 1.0.0
paths:
/test:
get:
parameters:
- $ref: "some-file-that-does-not-exist.yaml"`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(spec), &rootNode)
c := CreateOpenAPIIndexConfig()
c.FSHandler = FS{}
index := NewSpecIndexWithConfig(&rootNode, c)
// extract crs param from index
crsParam := index.GetMappedReferences()["some-file-that-does-not-exist.yaml"]
assert.NotNil(t, crsParam)
assert.True(t, crsParam.IsRemote)
assert.Equal(t, "string", crsParam.Node.Content[1].Value)
assert.Equal(t, "something", crsParam.Node.Content[3].Value)
assert.Equal(t, "query", crsParam.Node.Content[5].Value)
}
func TestSpecIndex_UseRemoteHandler_Error_Open(t *testing.T) { func TestSpecIndex_UseRemoteHandler_Error_Open(t *testing.T) {
spec := `openapi: 3.1.0 spec := `openapi: 3.1.0
@@ -317,7 +346,7 @@ paths:
_ = yaml.Unmarshal([]byte(spec), &rootNode) _ = yaml.Unmarshal([]byte(spec), &rootNode)
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
c.RemoteHandler = FSBadOpen{} c.FSHandler = FSBadOpen{}
c.RemoteURLHandler = httpClient.Get c.RemoteURLHandler = httpClient.Get
index := NewSpecIndexWithConfig(&rootNode, c) index := NewSpecIndexWithConfig(&rootNode, c)
@@ -327,6 +356,32 @@ paths:
assert.Equal(t, "component 'https://-i-cannot-be-opened.com' does not exist in the specification", index.GetReferenceIndexErrors()[1].Error()) assert.Equal(t, "component 'https://-i-cannot-be-opened.com' does not exist in the specification", index.GetReferenceIndexErrors()[1].Error())
} }
func TestSpecIndex_UseFileHandler_Error_Open(t *testing.T) {
spec := `openapi: 3.1.0
info:
title: Test File Handler
version: 1.0.0
paths:
/test:
get:
parameters:
- $ref: "I-can-never-be-opened.yaml"`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(spec), &rootNode)
c := CreateOpenAPIIndexConfig()
c.FSHandler = FSBadOpen{}
c.RemoteURLHandler = httpClient.Get
index := NewSpecIndexWithConfig(&rootNode, c)
assert.Len(t, index.GetReferenceIndexErrors(), 2)
assert.Equal(t, "unable to open file: bad file open", index.GetReferenceIndexErrors()[0].Error())
assert.Equal(t, "component 'I-can-never-be-opened.yaml' does not exist in the specification", index.GetReferenceIndexErrors()[1].Error())
}
func TestSpecIndex_UseRemoteHandler_Error_Read(t *testing.T) { func TestSpecIndex_UseRemoteHandler_Error_Read(t *testing.T) {
spec := `openapi: 3.1.0 spec := `openapi: 3.1.0
@@ -343,7 +398,7 @@ paths:
_ = yaml.Unmarshal([]byte(spec), &rootNode) _ = yaml.Unmarshal([]byte(spec), &rootNode)
c := CreateOpenAPIIndexConfig() c := CreateOpenAPIIndexConfig()
c.RemoteHandler = FSBadRead{} c.FSHandler = FSBadRead{}
c.RemoteURLHandler = httpClient.Get c.RemoteURLHandler = httpClient.Get
index := NewSpecIndexWithConfig(&rootNode, c) index := NewSpecIndexWithConfig(&rootNode, c)
@@ -352,3 +407,29 @@ paths:
assert.Equal(t, "unable to read remote file bytes: bad file read", index.GetReferenceIndexErrors()[0].Error()) assert.Equal(t, "unable to read remote file bytes: bad file read", index.GetReferenceIndexErrors()[0].Error())
assert.Equal(t, "component 'https://-i-cannot-be-opened.com' does not exist in the specification", index.GetReferenceIndexErrors()[1].Error()) assert.Equal(t, "component 'https://-i-cannot-be-opened.com' does not exist in the specification", index.GetReferenceIndexErrors()[1].Error())
} }
func TestSpecIndex_UseFileHandler_Error_Read(t *testing.T) {
spec := `openapi: 3.1.0
info:
title: Test File Handler
version: 1.0.0
paths:
/test:
get:
parameters:
- $ref: "I-am-impossible-to-open-forever.yaml"`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(spec), &rootNode)
c := CreateOpenAPIIndexConfig()
c.FSHandler = FSBadRead{}
c.RemoteURLHandler = httpClient.Get
index := NewSpecIndexWithConfig(&rootNode, c)
assert.Len(t, index.GetReferenceIndexErrors(), 2)
assert.Equal(t, "unable to read file bytes: bad file read", index.GetReferenceIndexErrors()[0].Error())
assert.Equal(t, "component 'I-am-impossible-to-open-forever.yaml' does not exist in the specification", index.GetReferenceIndexErrors()[1].Error())
}

View File

@@ -67,10 +67,19 @@ type SpecIndexConfig struct {
// Resolves [#132]: https://github.com/pb33f/libopenapi/issues/132 // Resolves [#132]: https://github.com/pb33f/libopenapi/issues/132
RemoteURLHandler func(url string) (*http.Response, error) RemoteURLHandler func(url string) (*http.Response, error)
// RemoteHandler is a function that will be used to fetch remote documents, it trumps the RemoteURLHandler // FSHandler is an entity that implements the `fs.FS` interface that will be used to fetch local or remote documents.
// and will be used instead if it is set. // This is useful if you want to use a custom file system handler, or if you want to use a custom http client or
// custom network implementation for a lookup.
//
// libopenapi will pass the path to the FSHandler, and it will be up to the handler to determine how to fetch
// the document. This is really useful if your application has a custom file system or uses a database for storing
// documents.
//
// Is the FSHandler is set, it will be used for all lookups, regardless of whether they are local or remote.
// it also overrides the RemoteURLHandler if set.
//
// Resolves[#85] https://github.com/pb33f/libopenapi/issues/85 // Resolves[#85] https://github.com/pb33f/libopenapi/issues/85
RemoteHandler fs.FS FSHandler fs.FS
// If resolving locally, the BasePath will be the root from which relative references will be resolved from // If resolving locally, the BasePath will be the root from which relative references will be resolved from
BasePath string // set the Base Path for resolving relative references if the spec is exploded. BasePath string // set the Base Path for resolving relative references if the spec is exploded.

View File

@@ -196,9 +196,9 @@ func FindFirstKeyNode(key string, nodes []*yaml.Node, depth int) (keyNode *yaml.
for i, v := range nodes { for i, v := range nodes {
if key != "" && key == v.Value { if key != "" && key == v.Value {
if i+1 >= len(nodes) { if i+1 >= len(nodes) {
return v, nodes[i] // this is the node we need. return v, NodeAlias(nodes[i]) // this is the node we need.
} }
return v, nodes[i+1] // next node is what we need. return v, NodeAlias(nodes[i+1]) // next node is what we need.
} }
if len(v.Content) > 0 { if len(v.Content) > 0 {
depth++ depth++
@@ -283,12 +283,12 @@ func FindKeyNodeFull(key string, nodes []*yaml.Node) (keyNode *yaml.Node, labelN
if key == v.Content[x].Value { if key == v.Content[x].Value {
if IsNodeMap(v) { if IsNodeMap(v) {
if x+1 == len(v.Content) { if x+1 == len(v.Content) {
return v, v.Content[x], v.Content[x] return v, v.Content[x], NodeAlias(v.Content[x])
} }
return v, v.Content[x], v.Content[x+1] return v, v.Content[x], NodeAlias(v.Content[x+1])
} }
if IsNodeArray(v) { if IsNodeArray(v) {
return v, v.Content[x], v.Content[x] return v, v.Content[x], NodeAlias(v.Content[x])
} }
} }
} }
@@ -304,7 +304,7 @@ func FindKeyNodeFullTop(key string, nodes []*yaml.Node) (keyNode *yaml.Node, lab
continue continue
} }
if i%2 == 0 && key == nodes[i].Value { if i%2 == 0 && key == nodes[i].Value {
return nodes[i], nodes[i], nodes[i+1] // next node is what we need. return nodes[i], nodes[i], NodeAlias(nodes[i+1]) // next node is what we need.
} }
} }
return nil, nil, nil return nil, nil, nil
@@ -322,7 +322,7 @@ func FindExtensionNodes(nodes []*yaml.Node) []*ExtensionNode {
if i+1 < len(nodes) { if i+1 < len(nodes) {
extensions = append(extensions, &ExtensionNode{ extensions = append(extensions, &ExtensionNode{
Key: v, Key: v,
Value: nodes[i+1], Value: NodeAlias(nodes[i+1]),
}) })
} }
} }
@@ -363,12 +363,38 @@ func IsNodeMap(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!map" n := NodeAlias(node)
return n.Tag == "!!map"
}
// IsNodeAlias checks if the node is an alias, and lifts out the anchor
func IsNodeAlias(node *yaml.Node) (*yaml.Node, bool) {
if node == nil {
return nil, false
}
if node.Kind == yaml.AliasNode {
node = node.Alias
return node, true
}
return node, false
}
// NodeAlias checks if the node is an alias, and lifts out the anchor
func NodeAlias(node *yaml.Node) *yaml.Node {
if node == nil {
return nil
}
if node.Kind == yaml.AliasNode {
node = node.Alias
return node
}
return node
} }
// IsNodePolyMorphic will return true if the node contains polymorphic keys. // IsNodePolyMorphic will return true if the node contains polymorphic keys.
func IsNodePolyMorphic(node *yaml.Node) bool { func IsNodePolyMorphic(node *yaml.Node) bool {
for i, v := range node.Content { n := NodeAlias(node)
for i, v := range n.Content {
if i%2 == 0 { if i%2 == 0 {
if v.Value == "anyOf" || v.Value == "oneOf" || v.Value == "allOf" { if v.Value == "anyOf" || v.Value == "oneOf" || v.Value == "allOf" {
return true return true
@@ -383,7 +409,8 @@ func IsNodeArray(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!seq" n := NodeAlias(node)
return n.Tag == "!!seq"
} }
// IsNodeStringValue checks if a node is a string value // IsNodeStringValue checks if a node is a string value
@@ -391,7 +418,8 @@ func IsNodeStringValue(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!str" n := NodeAlias(node)
return n.Tag == "!!str"
} }
// IsNodeIntValue will check if a node is an int value // IsNodeIntValue will check if a node is an int value
@@ -399,7 +427,8 @@ func IsNodeIntValue(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!int" n := NodeAlias(node)
return n.Tag == "!!int"
} }
// IsNodeFloatValue will check is a node is a float value. // IsNodeFloatValue will check is a node is a float value.
@@ -407,7 +436,8 @@ func IsNodeFloatValue(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!float" n := NodeAlias(node)
return n.Tag == "!!float"
} }
// IsNodeNumberValue will check if a node can be parsed as a float value. // IsNodeNumberValue will check if a node can be parsed as a float value.
@@ -423,18 +453,20 @@ func IsNodeBoolValue(node *yaml.Node) bool {
if node == nil { if node == nil {
return false return false
} }
return node.Tag == "!!bool" n := NodeAlias(node)
return n.Tag == "!!bool"
} }
func IsNodeRefValue(node *yaml.Node) (bool, *yaml.Node, string) { func IsNodeRefValue(node *yaml.Node) (bool, *yaml.Node, string) {
if node == nil { if node == nil {
return false, nil, "" return false, nil, ""
} }
n := NodeAlias(node)
for i, r := range node.Content { for i, r := range n.Content {
if i%2 == 0 { if i%2 == 0 {
if r.Value == "$ref" { if r.Value == "$ref" {
return true, r, node.Content[i+1].Value return true, r, n.Content[i+1].Value
} }
} }
} }
@@ -691,3 +723,26 @@ func DetermineWhitespaceLength(input string) int {
return 0 return 0
} }
} }
// CheckForMergeNodes will check the top level of the schema for merge nodes. If any are found, then the merged nodes
// will be appended to the end of the rest of the nodes in the schema.
// Note: this is a destructive operation, so the in-memory node structure will be modified
func CheckForMergeNodes(node *yaml.Node) {
if node == nil {
return
}
total := len(node.Content)
for i := 0; i < total; i++ {
mn := node.Content[i]
if i%2 == 0 {
if mn.Tag == "!!merge" {
an := node.Content[i+1].Alias
if an != nil {
node.Content = append(node.Content, an.Content...) // append the merged nodes
total = len(node.Content)
i += 2
}
}
}
}
}

View File

@@ -571,20 +571,20 @@ func TestIsNodeFloatValue(t *testing.T) {
} }
func TestIsNodeNumberValue(t *testing.T) { func TestIsNodeNumberValue(t *testing.T) {
n := &yaml.Node{ n := &yaml.Node{
Tag: "!!float", Tag: "!!float",
} }
assert.True(t, IsNodeNumberValue(n)) assert.True(t, IsNodeNumberValue(n))
n.Tag = "!!pizza" n.Tag = "!!pizza"
assert.False(t, IsNodeNumberValue(n)) assert.False(t, IsNodeNumberValue(n))
n = &yaml.Node{ n = &yaml.Node{
Tag: "!!int", Tag: "!!int",
} }
assert.True(t, IsNodeNumberValue(n)) assert.True(t, IsNodeNumberValue(n))
n.Tag = "!!pizza" n.Tag = "!!pizza"
assert.False(t, IsNodeNumberValue(n)) assert.False(t, IsNodeNumberValue(n))
assert.False(t, IsNodeNumberValue(nil)) assert.False(t, IsNodeNumberValue(nil))
} }
func TestIsNodeFloatValue_Nil(t *testing.T) { func TestIsNodeFloatValue_Nil(t *testing.T) {
@@ -767,6 +767,69 @@ func TestIsNodeRefValue(t *testing.T) {
} }
func TestIsNodeAlias(t *testing.T) {
yml := `things:
&anchorA
- Stuff
- Junk
thangs: *anchorA`
var node yaml.Node
_ = yaml.Unmarshal([]byte(yml), &node)
ref, a := IsNodeAlias(node.Content[0].Content[3])
assert.True(t, a)
assert.Len(t, ref.Content, 2)
}
func TestNodeAlias(t *testing.T) {
yml := `things:
&anchorA
- Stuff
- Junk
thangs: *anchorA`
var node yaml.Node
_ = yaml.Unmarshal([]byte(yml), &node)
ref := NodeAlias(node.Content[0].Content[3])
assert.Len(t, ref.Content, 2)
}
func TestCheckForMergeNodes(t *testing.T) {
yml := `x-common-definitions:
life_cycle_types: &life_cycle_types_def
type: string
enum: ["Onboarding", "Monitoring", "Re-Assessment"]
description: The type of life cycle
<<: *life_cycle_types_def`
var node yaml.Node
_ = yaml.Unmarshal([]byte(yml), &node)
mainNode := node.Content[0]
CheckForMergeNodes(mainNode)
_, _, enumVal := FindKeyNodeFullTop("enum", mainNode.Content)
_, _, descriptionVal := FindKeyNodeFullTop("description", mainNode.Content)
assert.Equal(t, "The type of life cycle", descriptionVal.Value)
assert.Len(t, enumVal.Content, 3)
}
func TestCheckForMergeNodes_Empty_NoPanic(t *testing.T) {
CheckForMergeNodes(nil)
}
func TestIsNodeRefValue_False(t *testing.T) { func TestIsNodeRefValue_False(t *testing.T) {
f := &yaml.Node{ f := &yaml.Node{