building out tests for extraction functions.

Same functions will power every other model. Found some opprotunities to add control over circular references, when and how to fire errors and what not. All in all, some robust code additions will improve the library significantly.
This commit is contained in:
Dave Shanley
2022-08-28 13:21:57 -04:00
parent 61a1317ffd
commit 2398f051b8
8 changed files with 522 additions and 135 deletions

View File

@@ -59,9 +59,14 @@ func (p *Paths) Build(root *yaml.Node, idx *index.SpecIndex) error {
eChan := make(chan error)
var buildPathItem = func(cNode, pNode *yaml.Node, b chan<- pathBuildResult, e chan<- error) {
if ok, _, _ := utils.IsNodeRefValue(pNode); ok {
r := low.LocateRefNode(pNode, idx)
r, err := low.LocateRefNode(pNode, idx)
if r != nil {
pNode = r
if err != nil {
if !idx.AllowCircularReferenceResolving() {
e <- fmt.Errorf("path item build failed: %s", err.Error())
}
}
} else {
e <- fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d",
pNode.Content[1].Value, pNode.Content[1].Line, pNode.Content[1].Column)

View File

@@ -124,9 +124,14 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
wg.Add(1)
if ok, _, _ := utils.IsNodeRefValue(pathNode); ok {
r := low.LocateRefNode(pathNode, idx)
r, err := low.LocateRefNode(pathNode, idx)
if r != nil {
pathNode = r
if err != nil {
if !idx.AllowCircularReferenceResolving() {
return fmt.Errorf("build schema failed: %s", err.Error())
}
}
} else {
return fmt.Errorf("path item build failed: cannot find reference: %s at line %d, col %d",
pathNode.Content[1].Value, pathNode.Content[1].Line, pathNode.Content[1].Column)
@@ -172,9 +177,14 @@ func (p *PathItem) Build(root *yaml.Node, idx *index.SpecIndex) error {
//build out the operation.
if ok, _, _ := utils.IsNodeRefValue(op.ValueNode); ok {
r := low.LocateRefNode(op.ValueNode, idx)
r, err := low.LocateRefNode(op.ValueNode, idx)
if r != nil {
op.ValueNode = r
if err != nil {
if !idx.AllowCircularReferenceResolving() {
errCh <- fmt.Errorf("build schema failed: %s", err.Error())
}
}
} else {
// any reference would be the second node.
errCh <- fmt.Errorf("cannot extract reference: %s", op.ValueNode.Content[1].Value)

View File

@@ -66,9 +66,14 @@ func (s *Schema) FindProperty(name string) *low.ValueReference[*SchemaProxy] {
func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
if h, _, _ := utils.IsNodeRefValue(root); h {
ref := low.LocateRefNode(root, idx)
ref, err := low.LocateRefNode(root, idx)
if ref != nil {
root = ref
if err != nil {
if !idx.AllowCircularReferenceResolving() {
return fmt.Errorf("build schema failed: %s", err.Error())
}
}
} else {
return fmt.Errorf("build schema failed: reference cannot be found: '%s', line %d, col %d",
root.Content[1].Value, root.Content[1].Line, root.Content[1].Column)
@@ -156,9 +161,14 @@ func (s *Schema) Build(root *yaml.Node, idx *index.SpecIndex) error {
// check our prop isn't reference
if h, _, _ := utils.IsNodeRefValue(prop); h {
ref := low.LocateRefNode(prop, idx)
ref, err := low.LocateRefNode(prop, idx)
if ref != nil {
prop = ref
if err != nil {
if !idx.AllowCircularReferenceResolving() {
return fmt.Errorf("build schema failed: %s", err.Error())
}
}
} else {
return fmt.Errorf("schema properties build failed: cannot find reference %s, line %d, col %d",
prop.Content[1].Value, prop.Content[1].Column, prop.Content[1].Line)
@@ -291,11 +301,6 @@ func countSubSchemaItems(node *yaml.Node) int {
return 0
}
type schemaBuildResult struct {
k low.KeyReference[string]
v low.ValueReference[*Schema]
}
type schemaProxyBuildResult struct {
k low.KeyReference[string]
v low.ValueReference[*SchemaProxy]
@@ -314,9 +319,14 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
// build out a SchemaProxy for every sub-schema.
build := func(kn *yaml.Node, vn *yaml.Node, c chan *low.ValueReference[*SchemaProxy], e chan error) {
if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := low.LocateRefNode(vn, idx)
ref, cerr := low.LocateRefNode(vn, idx)
if ref != nil {
vn = ref
if cerr != nil {
if !idx.AllowCircularReferenceResolving() {
e <- fmt.Errorf("build schema failed: %s", cerr.Error())
}
}
} else {
err := fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
vn.Content[1].Value, vn.Content[1].Line, vn.Content[1].Column)
@@ -344,9 +354,14 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
if utils.IsNodeMap(valueNode) {
if h, _, _ := utils.IsNodeRefValue(valueNode); h {
ref := low.LocateRefNode(valueNode, idx)
ref, err := low.LocateRefNode(valueNode, idx)
if ref != nil {
valueNode = ref
if err != nil {
if !idx.AllowCircularReferenceResolving() {
errors <- fmt.Errorf("build schema failed: %s", err.Error())
}
}
} else {
errors <- fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
valueNode.Content[1].Value, valueNode.Content[1].Line, valueNode.Content[1].Column)
@@ -375,9 +390,14 @@ func buildSchema(schemas chan schemaProxyBuildResult, labelNode, valueNode *yaml
refBuilds := 0
for _, vn := range valueNode.Content {
if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := low.LocateRefNode(vn, idx)
ref, err := low.LocateRefNode(vn, idx)
if ref != nil {
vn = ref
if err != nil {
if !idx.AllowCircularReferenceResolving() {
errors <- fmt.Errorf("build schema failed: %s", err.Error())
}
}
} else {
err := fmt.Errorf("build schema failed: reference cannot be found: %s, line %d, col %d",
vn.Content[1].Value, vn.Content[1].Line, vn.Content[1].Column)
@@ -412,10 +432,15 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S
errStr := "schema build failed: reference '%s' cannot be found at line %d, col %d"
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref := low.LocateRefNode(root, idx)
ref, err := low.LocateRefNode(root, idx)
if ref != nil {
schNode = ref
schLabel = rl
if err != nil {
if !idx.AllowCircularReferenceResolving() {
return nil, fmt.Errorf("build schema failed: %s", err.Error())
}
}
} else {
return nil, fmt.Errorf(errStr,
root.Content[1].Value, root.Content[1].Line, root.Content[1].Column)
@@ -424,9 +449,14 @@ func ExtractSchema(root *yaml.Node, idx *index.SpecIndex) (*low.NodeReference[*S
_, schLabel, schNode = utils.FindKeyNodeFull(SchemaLabel, root.Content)
if schNode != nil {
if h, _, _ := utils.IsNodeRefValue(schNode); h {
ref := low.LocateRefNode(schNode, idx)
ref, err := low.LocateRefNode(schNode, idx)
if ref != nil {
schNode = ref
if err != nil {
if !idx.AllowCircularReferenceResolving() {
return nil, fmt.Errorf("build schema failed: %s", err.Error())
}
}
} else {
return nil, fmt.Errorf(errStr,
schNode.Content[1].Value, schNode.Content[1].Line, schNode.Content[1].Column)

View File

@@ -740,8 +740,8 @@ allOf:
err := low.BuildModel(&idxNode, &sch)
assert.NoError(t, err)
_ = sch.Build(idxNode.Content[0], idx)
assert.Nil(t, sch.AllOf.Value[0].Value.Schema())
err = sch.Build(idxNode.Content[0], idx)
assert.Error(t, err)
}
@@ -916,12 +916,8 @@ func TestExtractSchema_RefRoot_Child_Fail(t *testing.T) {
var idxNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &idxNode)
s, _ := ExtractSchema(idxNode.Content[0], idx)
b := s.Value.Schema()
assert.Nil(t, b)
assert.Error(t, s.Value.GetBuildError())
_, err := ExtractSchema(idxNode.Content[0], idx)
assert.Error(t, err)
}
func TestExtractSchema_DoNothing(t *testing.T) {

View File

@@ -8,6 +8,7 @@ import (
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
"strconv"
"strings"
@@ -22,7 +23,7 @@ func FindItemInMap[T any](item string, collection map[KeyReference[string]]Value
return nil
}
func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) *yaml.Node {
func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) (*yaml.Node, error) {
if rf, _, rv := utils.IsNodeRefValue(root); rf {
// run through everything and return as soon as we find a match.
// this operates as fast as possible as ever
@@ -69,7 +70,30 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) *yaml.Node {
for _, collection := range collections {
found = collection()
if found != nil && found[rv] != nil {
return found[rv].Node
// if this is a ref node, we need to keep diving
// until we hit something that isn't a ref.
if jh, _, _ := utils.IsNodeRefValue(found[rv].Node); jh {
// if this node is circular, stop drop and roll.
if !IsCircular(found[rv].Node, idx) {
return LocateRefNode(found[rv].Node, idx)
} else {
Log.Error("circular reference found during lookup, and will remain un-resolved.",
zap.Int("line", found[rv].Node.Line),
zap.Int("column", found[rv].Node.Column),
zap.String("reference", found[rv].Definition),
zap.String("journey",
GetCircularReferenceResult(found[rv].Node, idx).GenerateJourneyPath()))
return found[rv].Node, fmt.Errorf("circular reference '%s' found during lookup at line %d, column %d, "+
"It cannot be resolved",
GetCircularReferenceResult(found[rv].Node, idx).GenerateJourneyPath(),
found[rv].Node.Line,
found[rv].Node.Column)
}
}
return found[rv].Node, nil
}
}
@@ -83,23 +107,28 @@ func LocateRefNode(root *yaml.Node, idx *index.SpecIndex) *yaml.Node {
nodes, fErr := path.Find(idx.GetRootNode())
if fErr == nil {
if len(nodes) > 0 {
return nodes[0]
return nodes[0], nil
}
}
}
return nil, fmt.Errorf("reference '%s' at line %d, column %d was not found", root.Value, root.Line, root.Column)
}
return nil
return nil, nil
}
func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (T, error) {
var circError error
if h, _, _ := utils.IsNodeRefValue(root); h {
ref := LocateRefNode(root, idx)
ref, err := LocateRefNode(root, idx)
if ref != nil {
root = ref
if err != nil {
circError = err
}
} else {
return nil, fmt.Errorf("object extraction failed: reference cannot be found: %s, line %d, col %d",
root.Content[1].Value, root.Content[1].Line, root.Content[1].Column)
if err != nil {
return nil, fmt.Errorf("object extraciton failed: %s", err.Error())
}
}
}
var n T = new(N)
@@ -111,31 +140,42 @@ func ExtractObjectRaw[T Buildable[N], N any](root *yaml.Node, idx *index.SpecInd
if err != nil {
return n, err
}
if circError != nil && !idx.AllowCircularReferenceResolving() {
return n, circError
}
return n, nil
}
func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (NodeReference[T], error) {
var ln, vn *yaml.Node
var circError error
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref := LocateRefNode(root, idx)
ref, err := LocateRefNode(root, idx)
if ref != nil {
vn = ref
ln = rl
if err != nil {
circError = err
}
} else {
return NodeReference[T]{}, fmt.Errorf("object build failed: reference cannot be found: %s",
root.Content[1].Value)
if err != nil {
return NodeReference[T]{}, fmt.Errorf("object extraciton failed: %s", err.Error())
}
}
} else {
_, ln, vn = utils.FindKeyNodeFull(label, root.Content)
if vn != nil {
if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := LocateRefNode(vn, idx)
ref, lerr := LocateRefNode(vn, idx)
if ref != nil {
vn = ref
if lerr != nil {
circError = lerr
}
} else {
return NodeReference[T]{}, fmt.Errorf("object build failed: reference cannot be found: %s",
vn.Content[1].Value)
if lerr != nil {
return NodeReference[T]{}, fmt.Errorf("object extraciton failed: %s", lerr.Error())
}
}
}
}
@@ -152,22 +192,29 @@ func ExtractObject[T Buildable[N], N any](label string, root *yaml.Node, idx *in
if err != nil {
return NodeReference[T]{}, err
}
return NodeReference[T]{
res := NodeReference[T]{
Value: n,
KeyNode: ln,
ValueNode: vn,
}, nil
}
if circError != nil && !idx.AllowCircularReferenceResolving() {
return res, err
}
return res, nil
}
func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) ([]ValueReference[T],
*yaml.Node, *yaml.Node, error) {
var ln, vn *yaml.Node
var circError error
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref := LocateRefNode(root, idx)
ref, err := LocateRefNode(root, idx)
if ref != nil {
vn = ref
ln = rl
if err != nil {
circError = err
}
} else {
return []ValueReference[T]{}, nil, nil, fmt.Errorf("array build failed: reference cannot be found: %s",
root.Content[1].Value)
@@ -176,12 +223,17 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind
_, ln, vn = utils.FindKeyNodeFull(label, root.Content)
if vn != nil {
if h, _, _ := utils.IsNodeRefValue(vn); h {
ref := LocateRefNode(vn, idx)
ref, err := LocateRefNode(vn, idx)
if ref != nil {
vn = ref
if err != nil {
circError = err
}
} else {
if err != nil {
return []ValueReference[T]{}, nil, nil, fmt.Errorf("array build failed: reference cannot be found: %s",
root.Content[1].Value)
err.Error())
}
}
}
}
@@ -193,12 +245,17 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind
}
for _, node := range vn.Content {
if rf, _, _ := utils.IsNodeRefValue(node); rf {
ref := LocateRefNode(node, idx)
ref, err := LocateRefNode(node, idx)
if ref != nil {
node = ref
if err != nil {
circError = err
}
} else {
if err != nil {
return []ValueReference[T]{}, nil, nil, fmt.Errorf("array build failed: reference cannot be found: %s",
root.Content[1].Value)
err.Error())
}
}
}
var n T = new(N)
@@ -216,6 +273,9 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind
})
}
}
if circError != nil && !idx.AllowCircularReferenceResolving() {
return items, ln, vn, circError
}
return items, ln, vn, nil
}
@@ -236,6 +296,7 @@ func ExtractExample(expNode, expLabel *yaml.Node) NodeReference[any] {
func ExtractMapFlatNoLookup[PT Buildable[N], N any](root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], error) {
valueMap := make(map[KeyReference[string]]ValueReference[PT])
var circError error
if utils.IsNodeMap(root) {
var currentKey *yaml.Node
skip := false
@@ -254,11 +315,16 @@ func ExtractMapFlatNoLookup[PT Buildable[N], N any](root *yaml.Node, idx *index.
}
// if value is a reference, we have to look it up in the index!
if h, _, _ := utils.IsNodeRefValue(node); h {
ref := LocateRefNode(node, idx)
ref, err := LocateRefNode(node, idx)
if ref != nil {
node = ref
if err != nil {
circError = err
}
} else {
return nil, fmt.Errorf("map build failed: reference cannot be found: %s", root.Content[1].Value)
if err != nil {
return nil, fmt.Errorf("map build failed: reference cannot be found: %s", err.Error())
}
}
}
@@ -280,6 +346,9 @@ func ExtractMapFlatNoLookup[PT Buildable[N], N any](root *yaml.Node, idx *index.
}
}
}
if circError != nil && !idx.AllowCircularReferenceResolving() {
return valueMap, circError
}
return valueMap, nil
}
@@ -290,12 +359,16 @@ type mappingResult[T any] struct {
func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]ValueReference[PT], *yaml.Node, *yaml.Node, error) {
var labelNode, valueNode *yaml.Node
var circError error
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref := LocateRefNode(root, idx)
ref, err := LocateRefNode(root, idx)
if ref != nil {
valueNode = ref
labelNode = rl
if err != nil {
circError = err
}
} else {
return nil, labelNode, valueNode, fmt.Errorf("map build failed: reference cannot be found: %s",
root.Content[1].Value)
@@ -304,12 +377,17 @@ func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *
_, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content)
if valueNode != nil {
if h, _, _ := utils.IsNodeRefValue(valueNode); h {
ref := LocateRefNode(valueNode, idx)
ref, err := LocateRefNode(valueNode, idx)
if ref != nil {
valueNode = ref
if err != nil {
circError = err
}
} else {
if err != nil {
return nil, labelNode, valueNode, fmt.Errorf("map build failed: reference cannot be found: %s",
root.Content[1].Value)
err.Error())
}
}
}
}
@@ -349,12 +427,17 @@ func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *
}
// check our valueNode isn't a reference still.
if h, _, _ := utils.IsNodeRefValue(en); h {
ref := LocateRefNode(en, idx)
ref, err := LocateRefNode(en, idx)
if ref != nil {
en = ref
if err != nil {
circError = err
}
} else {
if err != nil {
return nil, labelNode, valueNode, fmt.Errorf("flat map build failed: reference cannot be found: %s",
root.Content[1].Value)
err.Error())
}
}
}
@@ -377,85 +460,12 @@ func ExtractMapFlat[PT Buildable[N], N any](label string, root *yaml.Node, idx *
}
return valueMap, labelNode, valueNode, nil
}
if circError != nil && !idx.AllowCircularReferenceResolving() {
return nil, labelNode, valueNode, circError
}
return nil, labelNode, valueNode, nil
}
func ExtractMap[PT Buildable[N], N any](label string, root *yaml.Node, idx *index.SpecIndex) (map[KeyReference[string]]map[KeyReference[string]]ValueReference[PT], error) {
var labelNode, valueNode *yaml.Node
if rf, rl, _ := utils.IsNodeRefValue(root); rf {
// locate reference in index.
ref := LocateRefNode(root, idx)
if ref != nil {
valueNode = ref
labelNode = rl
} else {
return nil, fmt.Errorf("map build failed: reference cannot be found: %s", root.Content[1].Value)
}
} else {
_, labelNode, valueNode = utils.FindKeyNodeFull(label, root.Content)
if valueNode != nil {
if h, _, _ := utils.IsNodeRefValue(valueNode); h {
ref := LocateRefNode(valueNode, idx)
if ref != nil {
valueNode = ref
} else {
return nil, fmt.Errorf("map build failed: reference cannot be found: %s",
root.Content[1].Value)
}
}
}
}
if valueNode != nil {
var currentLabelNode *yaml.Node
valueMap := make(map[KeyReference[string]]ValueReference[PT])
for i, en := range valueNode.Content {
if i%2 == 0 {
currentLabelNode = en
continue
}
if strings.HasPrefix(strings.ToLower(currentLabelNode.Value), "x-") {
continue // yo, don't pay any attention to extensions, not here anyway.
}
// check our valueNode isn't a reference still.
if h, _, _ := utils.IsNodeRefValue(en); h {
ref := LocateRefNode(en, idx)
if ref != nil {
en = ref
} else {
return nil, fmt.Errorf("map build failed: reference cannot be found: %s",
root.Content[1].Value)
}
}
var n PT = new(N)
err := BuildModel(en, n)
if err != nil {
return nil, err
}
berr := n.Build(en, idx)
if berr != nil {
return nil, berr
}
valueMap[KeyReference[string]{
Value: currentLabelNode.Value,
KeyNode: currentLabelNode,
}] = ValueReference[PT]{
Value: n,
ValueNode: en,
}
}
resMap := make(map[KeyReference[string]]map[KeyReference[string]]ValueReference[PT])
resMap[KeyReference[string]{
Value: labelNode.Value,
KeyNode: labelNode,
}] = valueMap
return resMap, nil
}
return nil, nil
}
func ExtractExtensions(root *yaml.Node) map[KeyReference[string]]ValueReference[any] {
extensions := utils.FindExtensionNodes(root.Content)
extensionMap := make(map[KeyReference[string]]ValueReference[any])

View File

@@ -4,7 +4,9 @@
package low
import (
"fmt"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/resolver"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
@@ -47,7 +49,7 @@ func TestLocateRefNode(t *testing.T) {
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
located := LocateRefNode(cNode.Content[0], idx)
located, _ := LocateRefNode(cNode.Content[0], idx)
assert.NotNil(t, located)
}
@@ -68,7 +70,316 @@ func TestLocateRefNode_Path(t *testing.T) {
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
located := LocateRefNode(cNode.Content[0], idx)
located, _ := LocateRefNode(cNode.Content[0], idx)
assert.NotNil(t, located)
}
func TestLocateRefNode_Path_NotFound(t *testing.T) {
yml := `paths:
/burger/time:
description: hello`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `$ref: '#/paths/~1burger~1time-somethingsomethingdarkside-somethingsomethingcomplete'`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
located, err := LocateRefNode(cNode.Content[0], idx)
assert.Nil(t, located)
assert.Error(t, err)
}
type pizza struct {
Description NodeReference[string]
}
func (p *pizza) Build(n *yaml.Node, idx *index.SpecIndex) error {
return nil
}
func TestExtractObject(t *testing.T) {
yml := `components:
schemas:
pizza:
description: hello`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `tags:
description: hello pizza`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
tag, err := ExtractObject[*pizza]("tags", &cNode, idx)
assert.NoError(t, err)
assert.NotNil(t, tag)
assert.Equal(t, "hello pizza", tag.Value.Description.Value)
}
func TestExtractObject_Ref(t *testing.T) {
yml := `components:
schemas:
pizza:
description: hello pizza`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `tags:
$ref: '#/components/schemas/pizza'`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
tag, err := ExtractObject[*pizza]("tags", &cNode, idx)
assert.NoError(t, err)
assert.NotNil(t, tag)
assert.Equal(t, "hello pizza", tag.Value.Description.Value)
}
func TestExtractObject_DoubleRef(t *testing.T) {
yml := `components:
schemas:
cake:
description: cake time!
pizza:
$ref: '#/components/schemas/cake'`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `tags:
$ref: '#/components/schemas/pizza'`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
tag, err := ExtractObject[*pizza]("tags", &cNode, idx)
assert.NoError(t, err)
assert.NotNil(t, tag)
assert.Equal(t, "cake time!", tag.Value.Description.Value)
}
func TestExtractObject_DoubleRef_Circular(t *testing.T) {
yml := `components:
schemas:
loopy:
$ref: '#/components/schemas/cake'
cake:
$ref: '#/components/schemas/loopy'
pizza:
$ref: '#/components/schemas/cake'`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
// circular references are detected by the resolver, so lets run it!
resolv := resolver.NewResolver(idx)
assert.Len(t, resolv.CheckForCircularReferences(), 1)
yml = `tags:
$ref: '#/components/schemas/pizza'`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
tag, err := ExtractObject[*pizza]("tags", &cNode, idx)
assert.NoError(t, err)
assert.NotNil(t, tag)
assert.Equal(t, "", tag.Value.Description.Value)
assert.Equal(t, "cake -> loopy -> cake", idx.GetCircularReferences()[0].GenerateJourneyPath())
}
func TestExtractObject_DoubleRef_Circular_Fail(t *testing.T) {
yml := `components:
schemas:
loopy:
$ref: '#/components/schemas/cake'
cake:
$ref: '#/components/schemas/loopy'
pizza:
$ref: '#/components/schemas/cake'`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
// circular references are detected by the resolver, so lets run it!
resolv := resolver.NewResolver(idx)
assert.Len(t, resolv.CheckForCircularReferences(), 1)
yml = `tags:
$ref: #BORK`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
_, err := ExtractObject[*pizza]("tags", &cNode, idx)
assert.Error(t, err)
}
func TestExtractObject_DoubleRef_Circular_Direct(t *testing.T) {
yml := `components:
schemas:
loopy:
$ref: '#/components/schemas/cake'
cake:
$ref: '#/components/schemas/loopy'
pizza:
$ref: '#/components/schemas/cake'`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
// circular references are detected by the resolver, so lets run it!
resolv := resolver.NewResolver(idx)
assert.Len(t, resolv.CheckForCircularReferences(), 1)
yml = `$ref: '#/components/schemas/pizza'`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
tag, err := ExtractObject[*pizza]("tags", cNode.Content[0], idx)
assert.NoError(t, err)
assert.NotNil(t, tag)
assert.Equal(t, "", tag.Value.Description.Value)
assert.Equal(t, "cake -> loopy -> cake", idx.GetCircularReferences()[0].GenerateJourneyPath())
}
func TestExtractObject_DoubleRef_Circular_Direct_Fail(t *testing.T) {
yml := `components:
schemas:
loopy:
$ref: '#/components/schemas/cake'
cake:
$ref: '#/components/schemas/loopy'
pizza:
$ref: '#/components/schemas/cake'`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
// circular references are detected by the resolver, so lets run it!
resolv := resolver.NewResolver(idx)
assert.Len(t, resolv.CheckForCircularReferences(), 1)
yml = `$ref: '#/components/schemas/why-did-westworld-have-to-end-so-poorly-ffs'`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
_, err := ExtractObject[*pizza]("tags", cNode.Content[0], idx)
assert.Error(t, err)
}
type test_noGood struct {
DontWork int
}
func (t *test_noGood) Build(root *yaml.Node, idx *index.SpecIndex) error {
return fmt.Errorf("I am always going to fail")
}
type test_almostGood struct {
AlmostWork NodeReference[int]
}
func (t *test_almostGood) Build(root *yaml.Node, idx *index.SpecIndex) error {
return fmt.Errorf("I am always going to fail")
}
func TestExtractObject_BadLowLevelModel(t *testing.T) {
yml := `components:
schemas:
hey:`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `thing:
dontWork: 123`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
_, err := ExtractObject[*test_noGood]("thing", &cNode, idx)
assert.Error(t, err)
}
func TestExtractObject_BadBuild(t *testing.T) {
yml := `components:
schemas:
hey:`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `thing:
dontWork: 123`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
_, err := ExtractObject[*test_almostGood]("thing", &cNode, idx)
assert.Error(t, err)
}
func TestExtractObject_BadLabel(t *testing.T) {
yml := `components:
schemas:
hey:`
var idxNode yaml.Node
mErr := yaml.Unmarshal([]byte(yml), &idxNode)
assert.NoError(t, mErr)
idx := index.NewSpecIndex(&idxNode)
yml = `thing:
dontWork: 123`
var cNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &cNode)
res, err := ExtractObject[*test_almostGood]("ding", &cNode, idx)
assert.Nil(t, res.Value)
assert.NoError(t, err)
}

View File

@@ -67,3 +67,16 @@ func IsCircular(node *yaml.Node, idx *index.SpecIndex) bool {
}
return false
}
func GetCircularReferenceResult(node *yaml.Node, idx *index.SpecIndex) *index.CircularReferenceResult {
if idx == nil {
return nil // no index! nothing we can do.
}
refs := idx.GetCircularReferences()
for i := range idx.GetCircularReferences() {
if refs[i].LoopPoint.Node == node {
return refs[i]
}
}
return nil
}

View File

@@ -138,6 +138,7 @@ type SpecIndex struct {
seenRemoteSources map[string]*yaml.Node
remoteLock sync.Mutex
circularReferences []*CircularReferenceResult // only available when the resolver has been used.
allowCircularReferences bool // decide if you want to error out, or allow circular references, default is false.
}
// ExternalLookupFunction is for lookup functions that take a JSONSchema reference and tries to find that node in the
@@ -503,6 +504,17 @@ func (index *SpecIndex) GetAllExternalIndexes() map[string]*SpecIndex {
return index.externalSpecIndex
}
// SetAllowCircularReferenceResolving will flip a bit that can be used by any consumers to determine if they want
// to allow or disallow circular references to be resolved or visited
func (index *SpecIndex) SetAllowCircularReferenceResolving(allow bool) {
index.allowCircularReferences = allow
}
// AllowCircularReferenceResolving will return a bit that allows developers to determine what to do with circular refs.
func (index *SpecIndex) AllowCircularReferenceResolving() bool {
return index.allowCircularReferences
}
func (index *SpecIndex) checkPolymorphicNode(name string) (bool, string) {
switch name {
case "anyOf":