Files
libopenapi/index/resolver_test.go
quobix e624efbf84 addressed issue #195
Resolving and indexing has changed, new code is required and this isue highlighted a glitch introduced with the addition of the rolodex when resolving.

Signed-off-by: quobix <dave@quobix.com>
2023-11-08 12:36:08 -05:00

1093 lines
28 KiB
Go

package index
import (
"errors"
"fmt"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"net/http"
"net/url"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestNewResolver(t *testing.T) {
assert.Nil(t, NewResolver(nil))
}
func TestResolvingError_Error(t *testing.T) {
errs := []error{
&ResolvingError{
Path: "$.test1",
ErrorRef: errors.New("test1"),
Node: &yaml.Node{
Line: 1,
Column: 1,
},
},
&ResolvingError{
Path: "$.test2",
ErrorRef: errors.New("test2"),
Node: &yaml.Node{
Line: 1,
Column: 1,
},
},
}
assert.Equal(t, "test1: $.test1 [1:1]", errs[0].Error())
assert.Equal(t, "test2: $.test2 [1:1]", errs[1].Error())
}
func TestResolvingError_Error_Index(t *testing.T) {
errs := []error{
&ResolvingError{
ErrorRef: errors.Join(&IndexingError{
Path: "$.test1",
Err: errors.New("test1"),
Node: &yaml.Node{
Line: 1,
Column: 1,
},
}),
Node: &yaml.Node{
Line: 1,
Column: 1,
},
},
&ResolvingError{
ErrorRef: errors.Join(&IndexingError{
Path: "$.test2",
Err: errors.New("test2"),
Node: &yaml.Node{
Line: 1,
Column: 1,
},
}),
Node: &yaml.Node{
Line: 1,
Column: 1,
},
},
}
assert.Equal(t, "test1: $.test1 [1:1]", errs[0].Error())
assert.Equal(t, "test2: $.test2 [1:1]", errs[1].Error())
}
func Benchmark_ResolveDocumentStripe(b *testing.B) {
baseDir := "../test_specs/stripe.yaml"
resolveFile, _ := os.ReadFile(baseDir)
var rootNode yaml.Node
_ = yaml.Unmarshal(resolveFile, &rootNode)
for n := 0; n < b.N; n++ {
cf := CreateOpenAPIIndexConfig()
rolo := NewRolodex(cf)
rolo.SetRootNode(&rootNode)
indexedErr := rolo.IndexTheRolodex()
assert.Len(b, utils.UnwrapErrors(indexedErr), 3)
}
}
func TestResolver_ResolveComponents_CircularSpec(t *testing.T) {
circular, _ := os.ReadFile("../test_specs/circular-tests.yaml")
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
cf := CreateClosedAPIIndexConfig()
cf.AvoidCircularReferenceCheck = true
rolo := NewRolodex(cf)
rolo.SetRootNode(&rootNode)
indexedErr := rolo.IndexTheRolodex()
assert.NoError(t, indexedErr)
rolo.Resolve()
assert.Len(t, rolo.GetCaughtErrors(), 3)
_, err := yaml.Marshal(rolo.GetRootIndex().GetResolver().resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_CheckForCircularReferences(t *testing.T) {
circular, _ := os.ReadFile("../test_specs/circular-tests.yaml")
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
cf := CreateClosedAPIIndexConfig()
rolo := NewRolodex(cf)
rolo.SetRootNode(&rootNode)
indexedErr := rolo.IndexTheRolodex()
assert.Error(t, indexedErr)
assert.Len(t, utils.UnwrapErrors(indexedErr), 3)
rolo.CheckForCircularReferences()
assert.Len(t, rolo.GetCaughtErrors(), 3)
assert.Len(t, rolo.GetRootIndex().GetResolver().GetResolvingErrors(), 3)
assert.Len(t, rolo.GetRootIndex().GetResolver().GetInfiniteCircularReferences(), 3)
}
func TestResolver_CheckForCircularReferences_CatchArray(t *testing.T) {
circular := []byte(`openapi: 3.0.0
components:
schemas:
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "array"
items:
$ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`)
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 1)
assert.Len(t, resolver.GetResolvingErrors(), 1) // infinite loop is a resolving error.
assert.Len(t, resolver.GetInfiniteCircularReferences(), 1)
assert.True(t, resolver.GetInfiniteCircularReferences()[0].IsArrayResult)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_CheckForCircularReferences_IgnoreArray(t *testing.T) {
circular := []byte(`openapi: 3.0.0
components:
schemas:
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "array"
items:
$ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`)
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
resolver.IgnoreArrayCircularReferences()
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetResolvingErrors(), 0)
assert.Len(t, resolver.GetCircularReferences(), 0)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_CheckForCircularReferences_IgnorePoly_Any(t *testing.T) {
circular := []byte(`openapi: 3.0.0
components:
schemas:
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "object"
anyOf:
- $ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`)
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
resolver.IgnorePolymorphicCircularReferences()
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetResolvingErrors(), 0)
assert.Len(t, resolver.GetCircularReferences(), 0)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_CheckForCircularReferences_IgnorePoly_All(t *testing.T) {
circular := []byte(`openapi: 3.0.0
components:
schemas:
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "object"
allOf:
- $ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`)
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
resolver.IgnorePolymorphicCircularReferences()
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetResolvingErrors(), 0)
assert.Len(t, resolver.GetCircularReferences(), 0)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_CheckForCircularReferences_IgnorePoly_One(t *testing.T) {
circular := []byte(`openapi: 3.0.0
components:
schemas:
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "object"
oneOf:
- $ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`)
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
resolver.IgnorePolymorphicCircularReferences()
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetResolvingErrors(), 0)
assert.Len(t, resolver.GetCircularReferences(), 0)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_CheckForCircularReferences_CatchPoly_Any(t *testing.T) {
circular := []byte(`openapi: 3.0.0
components:
schemas:
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "object"
anyOf:
- $ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`)
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetResolvingErrors(), 0) // not an infinite loop if poly.
assert.Len(t, resolver.GetCircularReferences(), 1)
assert.Equal(t, "anyOf", resolver.GetCircularReferences()[0].PolymorphicType)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_CheckForCircularReferences_CatchPoly_All(t *testing.T) {
circular := []byte(`openapi: 3.0.0
components:
schemas:
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "object"
allOf:
- $ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`)
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetResolvingErrors(), 0) // not an infinite loop if poly.
assert.Len(t, resolver.GetCircularReferences(), 1)
assert.Equal(t, "allOf", resolver.GetCircularReferences()[0].PolymorphicType)
assert.True(t, resolver.GetCircularReferences()[0].IsPolymorphicResult)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_CircularReferencesRequiredValid(t *testing.T) {
circular, _ := os.ReadFile("../test_specs/swagger-valid-recursive-model.yaml")
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_CircularReferencesRequiredInvalid(t *testing.T) {
circular, _ := os.ReadFile("../test_specs/swagger-invalid-recursive-model.yaml")
var rootNode yaml.Node
_ = yaml.Unmarshal(circular, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 2)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_DeepJourney(t *testing.T) {
var journey []*Reference
for f := 0; f < 200; f++ {
journey = append(journey, nil)
}
idx := NewSpecIndexWithConfig(nil, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.Nil(t, resolver.extractRelatives(nil, nil, nil, nil, journey, false))
}
func TestResolver_ResolveComponents_Stripe_NoRolodex(t *testing.T) {
baseDir := "../test_specs/stripe.yaml"
resolveFile, _ := os.ReadFile(baseDir)
var stripeRoot yaml.Node
_ = yaml.Unmarshal(resolveFile, &stripeRoot)
info, _ := datamodel.ExtractSpecInfoWithDocumentCheck(resolveFile, true)
cf := CreateOpenAPIIndexConfig()
cf.SpecInfo = info
idx := NewSpecIndexWithConfig(&stripeRoot, cf)
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 3)
_, err := yaml.Marshal(resolver.resolvedRoot)
assert.NoError(t, err)
}
func TestResolver_ResolveComponents_Stripe(t *testing.T) {
baseDir := "../test_specs/stripe.yaml"
resolveFile, _ := os.ReadFile(baseDir)
var stripeRoot yaml.Node
_ = yaml.Unmarshal(resolveFile, &stripeRoot)
info, _ := datamodel.ExtractSpecInfoWithDocumentCheck(resolveFile, true)
cf := CreateOpenAPIIndexConfig()
cf.SpecInfo = info
cf.AvoidCircularReferenceCheck = true
rolo := NewRolodex(cf)
rolo.SetRootNode(&stripeRoot)
indexedErr := rolo.IndexTheRolodex()
assert.NoError(t, indexedErr)
// after resolving, the rolodex will have errors.
rolo.Resolve()
assert.Len(t, rolo.GetCaughtErrors(), 3)
assert.Len(t, rolo.GetRootIndex().GetResolver().GetNonPolymorphicCircularErrors(), 3)
assert.Len(t, rolo.GetRootIndex().GetResolver().GetPolymorphicCircularErrors(), 0)
}
func TestResolver_ResolveComponents_BurgerShop(t *testing.T) {
mixedref, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml")
var rootNode yaml.Node
_ = yaml.Unmarshal(mixedref, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.Resolve()
assert.Len(t, circ, 0)
}
func TestResolver_ResolveComponents_PolyNonCircRef(t *testing.T) {
yml := `paths:
/hey:
get:
responses:
"200":
$ref: '#/components/schemas/crackers'
components:
schemas:
cheese:
description: cheese
anyOf:
items:
$ref: '#/components/schemas/crackers'
crackers:
description: crackers
allOf:
- $ref: '#/components/schemas/tea'
tea:
description: tea`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0)
}
func TestResolver_ResolveComponents_PolyCircRef(t *testing.T) {
yml := `openapi: 3.1.0
components:
schemas:
cheese:
description: cheese
anyOf:
- $ref: '#/components/schemas/crackers'
crackers:
description: crackers
anyOf:
- $ref: '#/components/schemas/cheese'
tea:
description: tea`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
_ = resolver.CheckForCircularReferences()
resolver.circularReferences[0].IsInfiniteLoop = true // override
assert.Len(t, idx.GetCircularReferences(), 1)
assert.Len(t, resolver.GetPolymorphicCircularErrors(), 1)
assert.Equal(t, 2, idx.GetCircularReferences()[0].LoopIndex)
}
func TestResolver_ResolveComponents_Missing(t *testing.T) {
yml := `paths:
/hey:
get:
responses:
"200":
$ref: '#/components/schemas/crackers'
components:
schemas:
cheese:
description: cheese
properties:
thang:
$ref: '#/components/schemas/crackers'
crackers:
description: crackers
properties:
butter:
$ref: 'go home, I am drunk'`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
err := resolver.Resolve()
assert.Len(t, err, 2)
assert.Equal(t, "cannot resolve reference `go home, I am drunk`, it's missing: $go home, I am drunk [18:11]", err[0].Error())
}
func TestResolver_ResolveThroughPaths(t *testing.T) {
yml := `paths:
/pizza/{cake}/{pizza}/pie:
parameters:
- name: juicy
/companies/{companyId}/data/payments/{paymentId}:
get:
tags:
- Accounts receivable
parameters:
- $ref: '#/paths/~1pizza~1%7Bcake%7D~1%7Bpizza%7D~1pie/parameters/0'`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(yml), &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
err := resolver.Resolve()
assert.Len(t, err, 0)
}
func TestResolver_ResolveComponents_MixedRef(t *testing.T) {
mixedref, _ := os.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml")
var rootNode yaml.Node
_ = yaml.Unmarshal(mixedref, &rootNode)
// create a test server.
server := test_buildMixedRefServer()
defer server.Close()
// create a new config that allows local and remote to be mixed up.
cf := CreateOpenAPIIndexConfig()
cf.AvoidBuildIndex = true
cf.AllowRemoteLookup = true
cf.AvoidCircularReferenceCheck = true
cf.BasePath = "../test_specs"
// setting this baseURL will override the base
cf.BaseURL, _ = url.Parse(server.URL)
// create a new rolodex
rolo := NewRolodex(cf)
// set the rolodex root node to the root node of the spec.
rolo.SetRootNode(&rootNode)
// create a new remote fs and set the config for indexing.
remoteFS, _ := NewRemoteFSWithRootURL(server.URL)
remoteFS.SetIndexConfig(cf)
// set our remote handler func
c := http.Client{}
remoteFS.RemoteHandlerFunc = c.Get
// configure the local filesystem.
fsCfg := LocalFSConfig{
BaseDirectory: cf.BasePath,
FileFilters: []string{"burgershop.openapi.yaml"},
DirFS: os.DirFS(cf.BasePath),
}
// create a new local filesystem.
fileFS, err := NewLocalFSWithConfig(&fsCfg)
assert.NoError(t, err)
// add file systems to the rolodex
rolo.AddLocalFS(cf.BasePath, fileFS)
rolo.AddRemoteFS(server.URL, remoteFS)
// index the rolodex.
indexedErr := rolo.IndexTheRolodex()
assert.NoError(t, indexedErr)
rolo.Resolve()
index := rolo.GetRootIndex
resolver := index().GetResolver()
assert.Len(t, resolver.GetCircularReferences(), 0)
assert.Equal(t, 2, resolver.GetIndexesVisited())
// in v0.8.2 a new check was added when indexing, to prevent re-indexing the same file multiple times.
assert.Equal(t, 6, resolver.GetRelativesSeen())
assert.Equal(t, 5, resolver.GetJourneysTaken())
assert.Equal(t, 7, resolver.GetReferenceVisited())
}
func TestResolver_ResolveComponents_k8s(t *testing.T) {
k8s, _ := os.ReadFile("../test_specs/k8s.json")
var rootNode yaml.Node
_ = yaml.Unmarshal(k8s, &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.Resolve()
assert.Len(t, circ, 0)
}
// Example of how to resolve the Stripe OpenAPI specification, and check for circular reference errors
func ExampleNewResolver() {
// create a yaml.Node reference as a root node.
var rootNode yaml.Node
// load in the Stripe OpenAPI spec (lots of polymorphic complexity in here)
stripeBytes, _ := os.ReadFile("../test_specs/stripe.yaml")
// unmarshal bytes into our rootNode.
_ = yaml.Unmarshal(stripeBytes, &rootNode)
// create a new spec index (resolver depends on it)
indexConfig := CreateClosedAPIIndexConfig()
idx := NewSpecIndexWithConfig(&rootNode, indexConfig)
// create a new resolver using the index.
resolver := NewResolver(idx)
// resolve the document, if there are circular reference errors, they are returned/
// WARNING: this is a destructive action and the rootNode will be PERMANENTLY altered and cannot be unresolved
circularErrors := resolver.Resolve()
// The Stripe API has a bunch of circular reference problems, mainly from polymorphism.
// So let's print them out.
//
fmt.Printf("There are %d circular reference errors, %d of them are polymorphic errors, %d are not",
len(circularErrors), len(resolver.GetPolymorphicCircularErrors()), len(resolver.GetNonPolymorphicCircularErrors()))
// Output: There are 3 circular reference errors, 0 of them are polymorphic errors, 3 are not
}
func ExampleResolvingError() {
re := ResolvingError{
ErrorRef: errors.New("je suis une erreur"),
Node: &yaml.Node{
Line: 5,
Column: 21,
},
Path: "#/definitions/JeSuisUneErreur",
CircularReference: &CircularReferenceResult{},
}
fmt.Printf("%s", re.Error())
// Output: je suis une erreur: #/definitions/JeSuisUneErreur [5:21]
}
func TestDocument_IgnoreArrayCircularReferences(t *testing.T) {
var d = `openapi: 3.1.0
components:
schemas:
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "array"
items:
$ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(d), &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
resolver.IgnoreArrayCircularReferences()
assert.NotNil(t, resolver)
circ := resolver.Resolve()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetIgnoredCircularArrayReferences(), 1)
}
func TestDocument_IgnorePolyCircularReferences(t *testing.T) {
var d = `openapi: 3.1.0
components:
schemas:
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "object"
anyOf:
- $ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(d), &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
resolver.IgnorePolymorphicCircularReferences()
assert.NotNil(t, resolver)
circ := resolver.Resolve()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetIgnoredCircularPolyReferences(), 1)
}
func TestDocument_IgnorePolyCircularReferences_NoArrayForRef(t *testing.T) {
var d = `openapi: 3.1.0
components:
schemas:
bingo:
type: object
properties:
bango:
$ref: "#/components/schemas/ProductCategory"
ProductCategory:
type: "object"
properties:
name:
type: "string"
children:
type: "object"
items:
anyOf:
items:
$ref: "#/components/schemas/ProductCategory"
description: "Array of sub-categories in the same format."
required:
- "name"
- "children"`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(d), &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
resolver.IgnorePolymorphicCircularReferences()
assert.NotNil(t, resolver)
circ := resolver.Resolve()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetIgnoredCircularPolyReferences(), 1)
}
func TestResolver_isInfiniteCircularDep_NoRef(t *testing.T) {
resolver := NewResolver(nil)
a, b := resolver.isInfiniteCircularDependency(nil, nil, nil)
assert.False(t, a)
assert.Nil(t, b)
}
func TestResolver_AllowedCircle(t *testing.T) {
d := `openapi: 3.1.0
paths:
/test:
get:
responses:
'200':
description: OK
components:
schemas:
Obj:
type: object
properties:
other:
$ref: '#/components/schemas/Obj2'
Obj2:
type: object
properties:
other:
$ref: '#/components/schemas/Obj'
required:
- other`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(d), &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.Resolve()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetInfiniteCircularReferences(), 0)
assert.Len(t, resolver.GetSafeCircularReferences(), 1)
}
func TestResolver_AllowedCircle_Array(t *testing.T) {
d := `openapi: 3.1.0
components:
schemas:
Obj:
type: object
properties:
other:
$ref: '#/components/schemas/Obj2'
required:
- other
Obj2:
type: object
properties:
children:
type: array
items:
$ref: '#/components/schemas/Obj'
required:
- children`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(d), &rootNode)
cf := CreateClosedAPIIndexConfig()
cf.IgnoreArrayCircularReferences = true
idx := NewSpecIndexWithConfig(&rootNode, cf)
resolver := NewResolver(idx)
resolver.IgnoreArrayCircularReferences()
assert.NotNil(t, resolver)
circ := resolver.Resolve()
assert.Len(t, circ, 0)
assert.Len(t, resolver.GetInfiniteCircularReferences(), 0)
assert.Len(t, resolver.GetSafeCircularReferences(), 0)
assert.Len(t, resolver.GetIgnoredCircularArrayReferences(), 1)
}
func TestResolver_NotAllowedDeepCircle(t *testing.T) {
d := `openapi: 3.0
components:
schemas:
Three:
description: "test three"
properties:
bester:
"$ref": "#/components/schemas/Seven"
required:
- bester
Seven:
properties:
wow:
"$ref": "#/components/schemas/Three"
required:
- wow`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(d), &rootNode)
idx := NewSpecIndexWithConfig(&rootNode, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.NotNil(t, resolver)
circ := resolver.Resolve()
assert.Len(t, circ, 1)
assert.Len(t, resolver.GetInfiniteCircularReferences(), 1)
assert.Len(t, resolver.GetSafeCircularReferences(), 0)
}
func TestLocateRefEnd_WithResolve(t *testing.T) {
yml, _ := os.ReadFile("../../test_specs/first.yaml")
var bsn yaml.Node
_ = yaml.Unmarshal(yml, &bsn)
cf := CreateOpenAPIIndexConfig()
cf.BasePath = "../test_specs"
localFSConfig := &LocalFSConfig{
BaseDirectory: cf.BasePath,
FileFilters: []string{"first.yaml", "second.yaml", "third.yaml", "fourth.yaml"},
DirFS: os.DirFS(cf.BasePath),
}
localFs, _ := NewLocalFSWithConfig(localFSConfig)
rolo := NewRolodex(cf)
rolo.AddLocalFS(cf.BasePath, localFs)
rolo.SetRootNode(&bsn)
rolo.IndexTheRolodex()
wd, _ := os.Getwd()
cp, _ := filepath.Abs(filepath.Join(wd, "../test_specs/third.yaml"))
third := localFs.GetFiles()[cp]
refs := third.GetIndex().GetMappedReferences()
fullDef := fmt.Sprintf("%s#/properties/property/properties/statistics", cp)
ref := refs[fullDef]
assert.Equal(t, "statistics", ref.Name)
isRef, _, _ := utils.IsNodeRefValue(ref.Node)
assert.True(t, isRef)
// resolve the stack, it should convert the ref to a node.
rolo.Resolve()
isRef, _, _ = utils.IsNodeRefValue(ref.Node)
assert.False(t, isRef)
}
func TestResolveDoc_Issue195(t *testing.T) {
spec := `openapi: 3.0.1
info:
title: Some Example!
paths:
"/pet/findByStatus":
get:
responses:
default:
content:
application/json:
schema:
"$ref": https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml#/components/schemas/Error`
var rootNode yaml.Node
_ = yaml.Unmarshal([]byte(spec), &rootNode)
// create an index config
config := CreateOpenAPIIndexConfig()
// the rolodex will automatically try and check for circular references, you don't want to do this
// if you're resolving the spec, as the node tree is marked as 'seen' and you won't be able to resolve
// correctly.
config.AvoidCircularReferenceCheck = true
// new in 0.13+ is the ability to add remote and local file systems to the index
// requires a new part, the rolodex. It holds all the indexes and knows where to find
// every reference across local and remote files.
rolodex := NewRolodex(config)
// add a new remote file system.
remoteFS, _ := NewRemoteFSWithConfig(config)
// add the remote file system to the rolodex
rolodex.AddRemoteFS("", remoteFS)
// set the root node of the rolodex, this is your spec.
rolodex.SetRootNode(&rootNode)
// index the rolodex
indexingError := rolodex.IndexTheRolodex()
if indexingError != nil {
panic(indexingError)
}
// resolve the rolodex
rolodex.Resolve()
// there should be no errors at this point
resolvingErrors := rolodex.GetCaughtErrors()
if resolvingErrors != nil {
panic(resolvingErrors)
}
// perform some lookups.
var nodes []*yaml.Node
// pull out schema type
path, _ := yamlpath.NewPath("$.paths./pet/findByStatus.get.responses.default.content['application/json'].schema.type")
nodes, _ = path.Find(&rootNode)
assert.Equal(t, nodes[0].Value, "object")
// pull out required array
path, _ = yamlpath.NewPath("$.paths./pet/findByStatus.get.responses.default.content['application/json'].schema.required")
nodes, _ = path.Find(&rootNode)
assert.Equal(t, nodes[0].Content[0].Value, "code")
assert.Equal(t, nodes[0].Content[1].Value, "message")
}