mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-06 12:37:49 +00:00
fix for resolving looping relative references
In vacuum, a usecase was reported where an infinite loop occurred due to re-parsing the same reference over and over in a loop. It was re-creatable and it was because the loop happened before the index was ready. This should be resolved now, at least for this use case. To be sure, I have included the specs as a new test. https://github.com/daveshanley/vacuum/issues/268 Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
@@ -4,430 +4,429 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pb33f/libopenapi/utils"
|
"github.com/pb33f/libopenapi/utils"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExtractRefs will return a deduplicated slice of references for every unique ref found in the document.
|
// ExtractRefs will return a deduplicated slice of references for every unique ref found in the document.
|
||||||
// The total number of refs, will generally be much higher, you can extract those from GetRawReferenceCount()
|
// The total number of refs, will generally be much higher, you can extract those from GetRawReferenceCount()
|
||||||
func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, level int, poly bool, pName string) []*Reference {
|
func (index *SpecIndex) ExtractRefs(node, parent *yaml.Node, seenPath []string, level int, poly bool, pName string) []*Reference {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var found []*Reference
|
var found []*Reference
|
||||||
if len(node.Content) > 0 {
|
if len(node.Content) > 0 {
|
||||||
var prev, polyName string
|
var prev, polyName string
|
||||||
for i, n := range node.Content {
|
for i, n := range node.Content {
|
||||||
|
|
||||||
if utils.IsNodeMap(n) || utils.IsNodeArray(n) {
|
if utils.IsNodeMap(n) || utils.IsNodeArray(n) {
|
||||||
level++
|
level++
|
||||||
// check if we're using polymorphic values. These tend to create rabbit warrens of circular
|
// check if we're using polymorphic values. These tend to create rabbit warrens of circular
|
||||||
// references if every single link is followed. We don't resolve polymorphic values.
|
// references if every single link is followed. We don't resolve polymorphic values.
|
||||||
isPoly, _ := index.checkPolymorphicNode(prev)
|
isPoly, _ := index.checkPolymorphicNode(prev)
|
||||||
polyName = pName
|
polyName = pName
|
||||||
if isPoly {
|
if isPoly {
|
||||||
poly = true
|
poly = true
|
||||||
if prev != "" {
|
if prev != "" {
|
||||||
polyName = prev
|
polyName = prev
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
found = append(found, index.ExtractRefs(n, node, seenPath, level, poly, polyName)...)
|
found = append(found, index.ExtractRefs(n, node, seenPath, level, poly, polyName)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we're dealing with an inline schema definition, that isn't part of an array
|
// check if we're dealing with an inline schema definition, that isn't part of an array
|
||||||
// (which means it's being used as a value in an array, and it's not a label)
|
// (which means it's being used as a value in an array, and it's not a label)
|
||||||
// https://github.com/pb33f/libopenapi/issues/76
|
// https://github.com/pb33f/libopenapi/issues/76
|
||||||
if i%2 == 0 && n.Value == "schema" && !utils.IsNodeArray(node) && (i+1 < len(node.Content)) {
|
if i%2 == 0 && n.Value == "schema" && !utils.IsNodeArray(node) && (i+1 < len(node.Content)) {
|
||||||
isRef, _, _ := utils.IsNodeRefValue(node.Content[i+1])
|
isRef, _, _ := utils.IsNodeRefValue(node.Content[i+1])
|
||||||
if isRef {
|
if isRef {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ref := &Reference{
|
ref := &Reference{
|
||||||
Node: node.Content[i+1],
|
Node: node.Content[i+1],
|
||||||
Path: fmt.Sprintf("$.%s.schema", strings.Join(seenPath, ".")),
|
Path: fmt.Sprintf("$.%s.schema", strings.Join(seenPath, ".")),
|
||||||
}
|
}
|
||||||
index.allInlineSchemaDefinitions = append(index.allInlineSchemaDefinitions, ref)
|
index.allInlineSchemaDefinitions = append(index.allInlineSchemaDefinitions, ref)
|
||||||
|
|
||||||
// check if the schema is an object or an array,
|
// check if the schema is an object or an array,
|
||||||
// and if so, add it to the list of inline schema object definitions.
|
// and if so, add it to the list of inline schema object definitions.
|
||||||
k, v := utils.FindKeyNodeTop("type", node.Content[i+1].Content)
|
k, v := utils.FindKeyNodeTop("type", node.Content[i+1].Content)
|
||||||
if k != nil && v != nil {
|
if k != nil && v != nil {
|
||||||
if v.Value == "object" || v.Value == "array" {
|
if v.Value == "object" || v.Value == "array" {
|
||||||
index.allInlineSchemaObjectDefinitions = append(index.allInlineSchemaObjectDefinitions, ref)
|
index.allInlineSchemaObjectDefinitions = append(index.allInlineSchemaObjectDefinitions, ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the same check for all properties in an inline schema definition
|
// Perform the same check for all properties in an inline schema definition
|
||||||
// https://github.com/pb33f/libopenapi/issues/76
|
// https://github.com/pb33f/libopenapi/issues/76
|
||||||
if i%2 == 0 && n.Value == "properties" && !utils.IsNodeArray(node) && (i+1 < len(node.Content)) {
|
if i%2 == 0 && n.Value == "properties" && !utils.IsNodeArray(node) && (i+1 < len(node.Content)) {
|
||||||
isRef, _, _ := utils.IsNodeRefValue(node.Content[i+1])
|
isRef, _, _ := utils.IsNodeRefValue(node.Content[i+1])
|
||||||
if isRef {
|
if isRef {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// for each property add it to our schema definitions
|
// for each property add it to our schema definitions
|
||||||
label := ""
|
label := ""
|
||||||
for h, prop := range node.Content[i+1].Content {
|
for h, prop := range node.Content[i+1].Content {
|
||||||
|
|
||||||
if h%2 == 0 {
|
if h%2 == 0 {
|
||||||
label = prop.Value
|
label = prop.Value
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ref := &Reference{
|
ref := &Reference{
|
||||||
Node: prop,
|
Node: prop,
|
||||||
Path: fmt.Sprintf("$.%s.properties.%s", strings.Join(seenPath, "."), label),
|
Path: fmt.Sprintf("$.%s.properties.%s", strings.Join(seenPath, "."), label),
|
||||||
}
|
}
|
||||||
index.allInlineSchemaDefinitions = append(index.allInlineSchemaDefinitions, ref)
|
index.allInlineSchemaDefinitions = append(index.allInlineSchemaDefinitions, ref)
|
||||||
|
|
||||||
// check if the schema is an object or an array,
|
// check if the schema is an object or an array,
|
||||||
// and if so, add it to the list of inline schema object definitions.
|
// and if so, add it to the list of inline schema object definitions.
|
||||||
k, v := utils.FindKeyNodeTop("type", node.Content[i+1].Content)
|
k, v := utils.FindKeyNodeTop("type", node.Content[i+1].Content)
|
||||||
if k != nil && v != nil {
|
if k != nil && v != nil {
|
||||||
if v.Value == "object" || v.Value == "array" {
|
if v.Value == "object" || v.Value == "array" {
|
||||||
index.allInlineSchemaObjectDefinitions = append(index.allInlineSchemaObjectDefinitions, ref)
|
index.allInlineSchemaObjectDefinitions = append(index.allInlineSchemaObjectDefinitions, ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if i%2 == 0 && n.Value == "$ref" {
|
if i%2 == 0 && n.Value == "$ref" {
|
||||||
|
|
||||||
// only look at scalar values, not maps (looking at you k8s)
|
// only look at scalar values, not maps (looking at you k8s)
|
||||||
if !utils.IsNodeStringValue(node.Content[i+1]) {
|
if !utils.IsNodeStringValue(node.Content[i+1]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
index.linesWithRefs[n.Line] = true
|
index.linesWithRefs[n.Line] = true
|
||||||
|
|
||||||
fp := make([]string, len(seenPath))
|
fp := make([]string, len(seenPath))
|
||||||
for x, foundPathNode := range seenPath {
|
for x, foundPathNode := range seenPath {
|
||||||
fp[x] = foundPathNode
|
fp[x] = foundPathNode
|
||||||
}
|
}
|
||||||
|
|
||||||
value := node.Content[i+1].Value
|
value := node.Content[i+1].Value
|
||||||
|
|
||||||
segs := strings.Split(value, "/")
|
segs := strings.Split(value, "/")
|
||||||
name := segs[len(segs)-1]
|
name := segs[len(segs)-1]
|
||||||
_, p := utils.ConvertComponentIdIntoFriendlyPathSearch(value)
|
_, p := utils.ConvertComponentIdIntoFriendlyPathSearch(value)
|
||||||
ref := &Reference{
|
ref := &Reference{
|
||||||
Definition: value,
|
Definition: value,
|
||||||
Name: name,
|
Name: name,
|
||||||
Node: node,
|
Node: node,
|
||||||
Path: p,
|
Path: p,
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to raw sequenced refs
|
// add to raw sequenced refs
|
||||||
index.rawSequencedRefs = append(index.rawSequencedRefs, ref)
|
index.rawSequencedRefs = append(index.rawSequencedRefs, ref)
|
||||||
|
|
||||||
// add ref by line number
|
// add ref by line number
|
||||||
refNameIndex := strings.LastIndex(value, "/")
|
refNameIndex := strings.LastIndex(value, "/")
|
||||||
refName := value[refNameIndex+1:]
|
refName := value[refNameIndex+1:]
|
||||||
if len(index.refsByLine[refName]) > 0 {
|
if len(index.refsByLine[refName]) > 0 {
|
||||||
index.refsByLine[refName][n.Line] = true
|
index.refsByLine[refName][n.Line] = true
|
||||||
} else {
|
} else {
|
||||||
v := make(map[int]bool)
|
v := make(map[int]bool)
|
||||||
v[n.Line] = true
|
v[n.Line] = true
|
||||||
index.refsByLine[refName] = v
|
index.refsByLine[refName] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this ref value has any siblings (node.Content is larger than two elements)
|
// if this ref value has any siblings (node.Content is larger than two elements)
|
||||||
// then add to refs with siblings
|
// then add to refs with siblings
|
||||||
if len(node.Content) > 2 {
|
if len(node.Content) > 2 {
|
||||||
copiedNode := *node
|
copiedNode := *node
|
||||||
copied := Reference{
|
copied := Reference{
|
||||||
Definition: ref.Definition,
|
Definition: ref.Definition,
|
||||||
Name: ref.Name,
|
Name: ref.Name,
|
||||||
Node: &copiedNode,
|
Node: &copiedNode,
|
||||||
Path: p,
|
Path: p,
|
||||||
}
|
}
|
||||||
// protect this data using a copy, prevent the resolver from destroying things.
|
// protect this data using a copy, prevent the resolver from destroying things.
|
||||||
index.refsWithSiblings[value] = copied
|
index.refsWithSiblings[value] = copied
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is a polymorphic reference, we're going to leave it out
|
// if this is a polymorphic reference, we're going to leave it out
|
||||||
// allRefs. We don't ever want these resolved, so instead of polluting
|
// allRefs. We don't ever want these resolved, so instead of polluting
|
||||||
// the timeline, we will keep each poly ref in its own collection for later
|
// the timeline, we will keep each poly ref in its own collection for later
|
||||||
// analysis.
|
// analysis.
|
||||||
if poly {
|
if poly {
|
||||||
index.polymorphicRefs[value] = ref
|
index.polymorphicRefs[value] = ref
|
||||||
|
|
||||||
// index each type
|
// index each type
|
||||||
switch pName {
|
switch pName {
|
||||||
case "anyOf":
|
case "anyOf":
|
||||||
index.polymorphicAnyOfRefs = append(index.polymorphicAnyOfRefs, ref)
|
index.polymorphicAnyOfRefs = append(index.polymorphicAnyOfRefs, ref)
|
||||||
case "allOf":
|
case "allOf":
|
||||||
index.polymorphicAllOfRefs = append(index.polymorphicAllOfRefs, ref)
|
index.polymorphicAllOfRefs = append(index.polymorphicAllOfRefs, ref)
|
||||||
case "oneOf":
|
case "oneOf":
|
||||||
index.polymorphicOneOfRefs = append(index.polymorphicOneOfRefs, ref)
|
index.polymorphicOneOfRefs = append(index.polymorphicOneOfRefs, ref)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if this is a dupe, if so, skip it, we don't care now.
|
// check if this is a dupe, if so, skip it, we don't care now.
|
||||||
if index.allRefs[value] != nil { // seen before, skip.
|
if index.allRefs[value] != nil { // seen before, skip.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if value == "" {
|
if value == "" {
|
||||||
|
|
||||||
completedPath := fmt.Sprintf("$.%s", strings.Join(fp, "."))
|
completedPath := fmt.Sprintf("$.%s", strings.Join(fp, "."))
|
||||||
|
|
||||||
indexError := &IndexingError{
|
indexError := &IndexingError{
|
||||||
Err: errors.New("schema reference is empty and cannot be processed"),
|
Err: errors.New("schema reference is empty and cannot be processed"),
|
||||||
Node: node.Content[i+1],
|
Node: node.Content[i+1],
|
||||||
Path: completedPath,
|
Path: completedPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
index.refErrors = append(index.refErrors, indexError)
|
index.refErrors = append(index.refErrors, indexError)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
index.allRefs[value] = ref
|
index.allRefs[value] = ref
|
||||||
found = append(found, ref)
|
found = append(found, ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
if i%2 == 0 && n.Value != "$ref" && n.Value != "" {
|
if i%2 == 0 && n.Value != "$ref" && n.Value != "" {
|
||||||
|
|
||||||
nodePath := fmt.Sprintf("$.%s", strings.Join(seenPath, "."))
|
nodePath := fmt.Sprintf("$.%s", strings.Join(seenPath, "."))
|
||||||
|
|
||||||
// capture descriptions and summaries
|
// capture descriptions and summaries
|
||||||
if n.Value == "description" {
|
if n.Value == "description" {
|
||||||
|
|
||||||
// if the parent is a sequence, ignore.
|
// if the parent is a sequence, ignore.
|
||||||
if utils.IsNodeArray(node) {
|
if utils.IsNodeArray(node) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ref := &DescriptionReference{
|
ref := &DescriptionReference{
|
||||||
Content: node.Content[i+1].Value,
|
Content: node.Content[i+1].Value,
|
||||||
Path: nodePath,
|
Path: nodePath,
|
||||||
Node: node.Content[i+1],
|
Node: node.Content[i+1],
|
||||||
IsSummary: false,
|
IsSummary: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsNodeMap(ref.Node) {
|
if !utils.IsNodeMap(ref.Node) {
|
||||||
index.allDescriptions = append(index.allDescriptions, ref)
|
index.allDescriptions = append(index.allDescriptions, ref)
|
||||||
index.descriptionCount++
|
index.descriptionCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.Value == "summary" {
|
if n.Value == "summary" {
|
||||||
|
|
||||||
var b *yaml.Node
|
var b *yaml.Node
|
||||||
if len(node.Content) == i+1 {
|
if len(node.Content) == i+1 {
|
||||||
b = node.Content[i]
|
b = node.Content[i]
|
||||||
} else {
|
} else {
|
||||||
b = node.Content[i+1]
|
b = node.Content[i+1]
|
||||||
}
|
}
|
||||||
ref := &DescriptionReference{
|
ref := &DescriptionReference{
|
||||||
Content: b.Value,
|
Content: b.Value,
|
||||||
Path: nodePath,
|
Path: nodePath,
|
||||||
Node: b,
|
Node: b,
|
||||||
IsSummary: true,
|
IsSummary: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
index.allSummaries = append(index.allSummaries, ref)
|
index.allSummaries = append(index.allSummaries, ref)
|
||||||
index.summaryCount++
|
index.summaryCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
// capture security requirement references (these are not traditional references, but they
|
// capture security requirement references (these are not traditional references, but they
|
||||||
// are used as a look-up. This is the only exception to the design.
|
// are used as a look-up. This is the only exception to the design.
|
||||||
if n.Value == "security" {
|
if n.Value == "security" {
|
||||||
var b *yaml.Node
|
var b *yaml.Node
|
||||||
if len(node.Content) == i+1 {
|
if len(node.Content) == i+1 {
|
||||||
b = node.Content[i]
|
b = node.Content[i]
|
||||||
} else {
|
} else {
|
||||||
b = node.Content[i+1]
|
b = node.Content[i+1]
|
||||||
}
|
}
|
||||||
if utils.IsNodeArray(b) {
|
if utils.IsNodeArray(b) {
|
||||||
var secKey string
|
var secKey string
|
||||||
for k := range b.Content {
|
for k := range b.Content {
|
||||||
if utils.IsNodeMap(b.Content[k]) {
|
if utils.IsNodeMap(b.Content[k]) {
|
||||||
for g := range b.Content[k].Content {
|
for g := range b.Content[k].Content {
|
||||||
if g%2 == 0 {
|
if g%2 == 0 {
|
||||||
secKey = b.Content[k].Content[g].Value
|
secKey = b.Content[k].Content[g].Value
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if utils.IsNodeArray(b.Content[k].Content[g]) {
|
if utils.IsNodeArray(b.Content[k].Content[g]) {
|
||||||
var refMap map[string][]*Reference
|
var refMap map[string][]*Reference
|
||||||
if index.securityRequirementRefs[secKey] == nil {
|
if index.securityRequirementRefs[secKey] == nil {
|
||||||
index.securityRequirementRefs[secKey] = make(map[string][]*Reference)
|
index.securityRequirementRefs[secKey] = make(map[string][]*Reference)
|
||||||
refMap = index.securityRequirementRefs[secKey]
|
refMap = index.securityRequirementRefs[secKey]
|
||||||
} else {
|
} else {
|
||||||
refMap = index.securityRequirementRefs[secKey]
|
refMap = index.securityRequirementRefs[secKey]
|
||||||
}
|
}
|
||||||
for r := range b.Content[k].Content[g].Content {
|
for r := range b.Content[k].Content[g].Content {
|
||||||
var refs []*Reference
|
var refs []*Reference
|
||||||
if refMap[b.Content[k].Content[g].Content[r].Value] != nil {
|
if refMap[b.Content[k].Content[g].Content[r].Value] != nil {
|
||||||
refs = refMap[b.Content[k].Content[g].Content[r].Value]
|
refs = refMap[b.Content[k].Content[g].Content[r].Value]
|
||||||
}
|
}
|
||||||
|
|
||||||
refs = append(refs, &Reference{
|
refs = append(refs, &Reference{
|
||||||
Definition: b.Content[k].Content[g].Content[r].Value,
|
Definition: b.Content[k].Content[g].Content[r].Value,
|
||||||
Path: fmt.Sprintf("%s.security[%d].%s[%d]", nodePath, k, secKey, r),
|
Path: fmt.Sprintf("%s.security[%d].%s[%d]", nodePath, k, secKey, r),
|
||||||
Node: b.Content[k].Content[g].Content[r],
|
Node: b.Content[k].Content[g].Content[r],
|
||||||
})
|
})
|
||||||
|
|
||||||
index.securityRequirementRefs[secKey][b.Content[k].Content[g].Content[r].Value] = refs
|
index.securityRequirementRefs[secKey][b.Content[k].Content[g].Content[r].Value] = refs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// capture enums
|
// capture enums
|
||||||
if n.Value == "enum" {
|
if n.Value == "enum" {
|
||||||
|
|
||||||
// all enums need to have a type, extract the type from the node where the enum was found.
|
// all enums need to have a type, extract the type from the node where the enum was found.
|
||||||
_, enumKeyValueNode := utils.FindKeyNodeTop("type", node.Content)
|
_, enumKeyValueNode := utils.FindKeyNodeTop("type", node.Content)
|
||||||
|
|
||||||
if enumKeyValueNode != nil {
|
if enumKeyValueNode != nil {
|
||||||
ref := &EnumReference{
|
ref := &EnumReference{
|
||||||
Path: nodePath,
|
Path: nodePath,
|
||||||
Node: node.Content[i+1],
|
Node: node.Content[i+1],
|
||||||
Type: enumKeyValueNode,
|
Type: enumKeyValueNode,
|
||||||
SchemaNode: node,
|
SchemaNode: node,
|
||||||
ParentNode: parent,
|
ParentNode: parent,
|
||||||
}
|
}
|
||||||
|
|
||||||
index.allEnums = append(index.allEnums, ref)
|
index.allEnums = append(index.allEnums, ref)
|
||||||
index.enumCount++
|
index.enumCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// capture all objects with properties
|
// capture all objects with properties
|
||||||
if n.Value == "properties" {
|
if n.Value == "properties" {
|
||||||
_, typeKeyValueNode := utils.FindKeyNodeTop("type", node.Content)
|
_, typeKeyValueNode := utils.FindKeyNodeTop("type", node.Content)
|
||||||
|
|
||||||
if typeKeyValueNode != nil {
|
if typeKeyValueNode != nil {
|
||||||
isObject := false
|
isObject := false
|
||||||
|
|
||||||
if typeKeyValueNode.Value == "object" {
|
if typeKeyValueNode.Value == "object" {
|
||||||
isObject = true
|
isObject = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range typeKeyValueNode.Content {
|
for _, v := range typeKeyValueNode.Content {
|
||||||
if v.Value == "object" {
|
if v.Value == "object" {
|
||||||
isObject = true
|
isObject = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isObject {
|
if isObject {
|
||||||
index.allObjectsWithProperties = append(index.allObjectsWithProperties, &ObjectReference{
|
index.allObjectsWithProperties = append(index.allObjectsWithProperties, &ObjectReference{
|
||||||
Path: nodePath,
|
Path: nodePath,
|
||||||
Node: node,
|
Node: node,
|
||||||
ParentNode: parent,
|
ParentNode: parent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
seenPath = append(seenPath, n.Value)
|
seenPath = append(seenPath, n.Value)
|
||||||
prev = n.Value
|
prev = n.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// if next node is map, don't add segment.
|
// if next node is map, don't add segment.
|
||||||
if i < len(node.Content)-1 {
|
if i < len(node.Content)-1 {
|
||||||
next := node.Content[i+1]
|
next := node.Content[i+1]
|
||||||
|
|
||||||
if i%2 != 0 && next != nil && !utils.IsNodeArray(next) && !utils.IsNodeMap(next) {
|
if i%2 != 0 && next != nil && !utils.IsNodeArray(next) && !utils.IsNodeMap(next) {
|
||||||
seenPath = seenPath[:len(seenPath)-1]
|
seenPath = seenPath[:len(seenPath)-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(seenPath) > 0 {
|
if len(seenPath) > 0 {
|
||||||
seenPath = seenPath[:len(seenPath)-1]
|
seenPath = seenPath[:len(seenPath)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if len(seenPath) > 0 {
|
if len(seenPath) > 0 {
|
||||||
seenPath = seenPath[:len(seenPath)-1]
|
seenPath = seenPath[:len(seenPath)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
index.refCount = len(index.allRefs)
|
index.refCount = len(index.allRefs)
|
||||||
|
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractComponentsFromRefs returns located components from references. The returned nodes from here
|
// ExtractComponentsFromRefs returns located components from references. The returned nodes from here
|
||||||
// can be used for resolving as they contain the actual object properties.
|
// can be used for resolving as they contain the actual object properties.
|
||||||
func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Reference {
|
func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Reference {
|
||||||
var found []*Reference
|
var found []*Reference
|
||||||
|
|
||||||
//run this async because when things get recursive, it can take a while
|
//run this async because when things get recursive, it can take a while
|
||||||
c := make(chan bool)
|
c := make(chan bool)
|
||||||
|
|
||||||
locate := func(ref *Reference, refIndex int, sequence []*ReferenceMapped) {
|
|
||||||
located := index.FindComponent(ref.Definition, ref.Node)
|
|
||||||
if located != nil {
|
|
||||||
index.refLock.Lock()
|
|
||||||
if index.allMappedRefs[ref.Definition] == nil {
|
|
||||||
found = append(found, located)
|
|
||||||
index.allMappedRefs[ref.Definition] = located
|
|
||||||
sequence[refIndex] = &ReferenceMapped{
|
|
||||||
Reference: located,
|
|
||||||
Definition: ref.Definition,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
index.refLock.Unlock()
|
|
||||||
} else {
|
|
||||||
|
|
||||||
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(ref.Definition)
|
locate := func(ref *Reference, refIndex int, sequence []*ReferenceMapped) {
|
||||||
indexError := &IndexingError{
|
located := index.FindComponent(ref.Definition, ref.Node)
|
||||||
Err: fmt.Errorf("component '%s' does not exist in the specification", ref.Definition),
|
if located != nil {
|
||||||
Node: ref.Node,
|
index.refLock.Lock()
|
||||||
Path: path,
|
if index.allMappedRefs[ref.Definition] == nil {
|
||||||
}
|
found = append(found, located)
|
||||||
index.errorLock.Lock()
|
index.allMappedRefs[ref.Definition] = located
|
||||||
index.refErrors = append(index.refErrors, indexError)
|
sequence[refIndex] = &ReferenceMapped{
|
||||||
index.errorLock.Unlock()
|
Reference: located,
|
||||||
}
|
Definition: ref.Definition,
|
||||||
c <- true
|
}
|
||||||
}
|
}
|
||||||
|
index.refLock.Unlock()
|
||||||
|
} else {
|
||||||
|
|
||||||
var refsToCheck []*Reference
|
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(ref.Definition)
|
||||||
for _, ref := range refs {
|
indexError := &IndexingError{
|
||||||
|
Err: fmt.Errorf("component '%s' does not exist in the specification", ref.Definition),
|
||||||
|
Node: ref.Node,
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
index.errorLock.Lock()
|
||||||
|
index.refErrors = append(index.refErrors, indexError)
|
||||||
|
index.errorLock.Unlock()
|
||||||
|
}
|
||||||
|
c <- true
|
||||||
|
}
|
||||||
|
|
||||||
// check reference for backslashes (hah yeah seen this too!)
|
var refsToCheck []*Reference
|
||||||
if strings.Contains(ref.Definition, "\\") { // this was from blazemeter.com haha!
|
for _, ref := range refs {
|
||||||
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(ref.Definition)
|
|
||||||
indexError := &IndexingError{
|
|
||||||
Err: fmt.Errorf("component '%s' contains a backslash '\\'. It's not valid", ref.Definition),
|
|
||||||
Node: ref.Node,
|
|
||||||
Path: path,
|
|
||||||
}
|
|
||||||
index.refErrors = append(index.refErrors, indexError)
|
|
||||||
continue
|
|
||||||
|
|
||||||
}
|
// check reference for backslashes (hah yeah seen this too!)
|
||||||
refsToCheck = append(refsToCheck, ref)
|
if strings.Contains(ref.Definition, "\\") { // this was from blazemeter.com haha!
|
||||||
}
|
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(ref.Definition)
|
||||||
mappedRefsInSequence := make([]*ReferenceMapped, len(refsToCheck))
|
indexError := &IndexingError{
|
||||||
|
Err: fmt.Errorf("component '%s' contains a backslash '\\'. It's not valid", ref.Definition),
|
||||||
|
Node: ref.Node,
|
||||||
|
Path: path,
|
||||||
|
}
|
||||||
|
index.refErrors = append(index.refErrors, indexError)
|
||||||
|
continue
|
||||||
|
|
||||||
for r := range refsToCheck {
|
}
|
||||||
// expand our index of all mapped refs
|
refsToCheck = append(refsToCheck, ref)
|
||||||
go locate(refsToCheck[r], r, mappedRefsInSequence)
|
}
|
||||||
//locate(refsToCheck[r], r, mappedRefsInSequence) // used for sync testing.
|
mappedRefsInSequence := make([]*ReferenceMapped, len(refsToCheck))
|
||||||
}
|
|
||||||
|
|
||||||
completedRefs := 0
|
for r := range refsToCheck {
|
||||||
for completedRefs < len(refsToCheck) {
|
// expand our index of all mapped refs
|
||||||
select {
|
go locate(refsToCheck[r], r, mappedRefsInSequence)
|
||||||
case <-c:
|
//locate(refsToCheck[r], r, mappedRefsInSequence) // used for sync testing.
|
||||||
completedRefs++
|
}
|
||||||
}
|
|
||||||
}
|
completedRefs := 0
|
||||||
for m := range mappedRefsInSequence {
|
for completedRefs < len(refsToCheck) {
|
||||||
if mappedRefsInSequence[m] != nil {
|
select {
|
||||||
index.allMappedRefsSequenced = append(index.allMappedRefsSequenced, mappedRefsInSequence[m])
|
case <-c:
|
||||||
}
|
completedRefs++
|
||||||
}
|
}
|
||||||
return found
|
}
|
||||||
|
for m := range mappedRefsInSequence {
|
||||||
|
if mappedRefsInSequence[m] != nil {
|
||||||
|
index.allMappedRefsSequenced = append(index.allMappedRefsSequenced, mappedRefsInSequence[m])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -267,11 +267,16 @@ func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference {
|
|||||||
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]
|
||||||
|
if res[0].Kind == yaml.DocumentNode {
|
||||||
|
resNode = res[0].Content[0]
|
||||||
|
}
|
||||||
ref := &Reference{
|
ref := &Reference{
|
||||||
Definition: componentId,
|
Definition: componentId,
|
||||||
Name: name,
|
Name: name,
|
||||||
Node: res[0],
|
Node: resNode,
|
||||||
Path: friendlySearch,
|
Path: friendlySearch,
|
||||||
RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}),
|
RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}),
|
||||||
}
|
}
|
||||||
@@ -283,8 +288,7 @@ func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (index *SpecIndex) performExternalLookup(uri []string, componentId string,
|
func (index *SpecIndex) performExternalLookup(uri []string, componentId string,
|
||||||
lookupFunction ExternalLookupFunction, parent *yaml.Node,
|
lookupFunction ExternalLookupFunction, parent *yaml.Node) *Reference {
|
||||||
) *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]]
|
||||||
@@ -349,21 +353,29 @@ func (index *SpecIndex) performExternalLookup(uri []string, componentId string,
|
|||||||
BasePath: newBasePath,
|
BasePath: newBasePath,
|
||||||
AllowRemoteLookup: index.config.AllowRemoteLookup,
|
AllowRemoteLookup: index.config.AllowRemoteLookup,
|
||||||
AllowFileLookup: index.config.AllowFileLookup,
|
AllowFileLookup: index.config.AllowFileLookup,
|
||||||
|
ParentIndex: index,
|
||||||
seenRemoteSources: index.config.seenRemoteSources,
|
seenRemoteSources: index.config.seenRemoteSources,
|
||||||
remoteLock: index.config.remoteLock,
|
remoteLock: index.config.remoteLock,
|
||||||
|
uri: uri,
|
||||||
}
|
}
|
||||||
|
|
||||||
var newIndex *SpecIndex
|
var newIndex *SpecIndex
|
||||||
newIndex = NewSpecIndexWithConfig(newRoot, newConfig)
|
seen := index.SearchAncestryForSeenURI(uri[0])
|
||||||
index.refLock.Lock()
|
if seen == nil {
|
||||||
index.externalLock.Lock()
|
|
||||||
index.externalSpecIndex[uri[0]] = newIndex
|
newIndex = NewSpecIndexWithConfig(newRoot, newConfig)
|
||||||
index.externalLock.Unlock()
|
index.refLock.Lock()
|
||||||
newIndex.relativePath = path
|
index.externalLock.Lock()
|
||||||
newIndex.parentIndex = index
|
index.externalSpecIndex[uri[0]] = newIndex
|
||||||
index.AddChild(newIndex)
|
index.externalLock.Unlock()
|
||||||
index.refLock.Unlock()
|
newIndex.relativePath = path
|
||||||
externalSpecIndex = newIndex
|
newIndex.parentIndex = index
|
||||||
|
index.AddChild(newIndex)
|
||||||
|
index.refLock.Unlock()
|
||||||
|
externalSpecIndex = newIndex
|
||||||
|
} else {
|
||||||
|
externalSpecIndex = seen
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package index
|
|||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,6 +27,22 @@ func TestSpecIndex_performExternalLookup(t *testing.T) {
|
|||||||
assert.Len(t, index.GetPathsNode().Content, 1)
|
assert.Len(t, index.GetPathsNode().Content, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSpecIndex_CheckCircularIndex(t *testing.T) {
|
||||||
|
yml, _ := os.ReadFile("../test_specs/first.yaml")
|
||||||
|
var rootNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(yml), &rootNode)
|
||||||
|
|
||||||
|
c := CreateOpenAPIIndexConfig()
|
||||||
|
c.BasePath = "../test_specs"
|
||||||
|
index := NewSpecIndexWithConfig(&rootNode, c)
|
||||||
|
assert.Nil(t, index.uri)
|
||||||
|
assert.NotNil(t, index.children[0].uri)
|
||||||
|
assert.NotNil(t, index.children[0].children[0].uri)
|
||||||
|
assert.NotNil(t, index.SearchIndexForReference("second.yaml#/properties/property2"))
|
||||||
|
assert.NotNil(t, index.SearchIndexForReference("second.yaml"))
|
||||||
|
assert.Nil(t, index.SearchIndexForReference("fourth.yaml"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestSpecIndex_performExternalLookup_invalidURL(t *testing.T) {
|
func TestSpecIndex_performExternalLookup_invalidURL(t *testing.T) {
|
||||||
yml := `openapi: 3.1.0
|
yml := `openapi: 3.1.0
|
||||||
components:
|
components:
|
||||||
|
|||||||
@@ -4,77 +4,82 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/sync/syncmap"
|
"golang.org/x/sync/syncmap"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants used to determine if resolving is local, file based or remote file based.
|
// Constants used to determine if resolving is local, file based or remote file based.
|
||||||
const (
|
const (
|
||||||
LocalResolve = iota
|
LocalResolve = iota
|
||||||
HttpResolve
|
HttpResolve
|
||||||
FileResolve
|
FileResolve
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reference is a wrapper around *yaml.Node results to make things more manageable when performing
|
// Reference is a wrapper around *yaml.Node results to make things more manageable when performing
|
||||||
// algorithms on data models. the *yaml.Node def is just a bit too low level for tracking state.
|
// algorithms on data models. the *yaml.Node def is just a bit too low level for tracking state.
|
||||||
type Reference struct {
|
type Reference struct {
|
||||||
Definition string
|
Definition string
|
||||||
Name string
|
Name string
|
||||||
Node *yaml.Node
|
Node *yaml.Node
|
||||||
ParentNode *yaml.Node
|
ParentNode *yaml.Node
|
||||||
Resolved bool
|
Resolved bool
|
||||||
Circular bool
|
Circular bool
|
||||||
Seen bool
|
Seen bool
|
||||||
IsRemote bool
|
IsRemote bool
|
||||||
RemoteLocation string
|
RemoteLocation string
|
||||||
Path string // this won't always be available.
|
Path string // this won't always be available.
|
||||||
RequiredRefProperties map[string][]string // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition
|
RequiredRefProperties map[string][]string // definition names (eg, #/definitions/One) to a list of required properties on this definition which reference that definition
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key)
|
// ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key)
|
||||||
type ReferenceMapped struct {
|
type ReferenceMapped struct {
|
||||||
Reference *Reference
|
Reference *Reference
|
||||||
Definition string
|
Definition string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpecIndexConfig is a configuration struct for the SpecIndex introduced in 0.6.0 that provides an expandable
|
// SpecIndexConfig is a configuration struct for the SpecIndex introduced in 0.6.0 that provides an expandable
|
||||||
// set of granular options. The first being the ability to set the Base URL for resolving relative references, and
|
// set of granular options. The first being the ability to set the Base URL for resolving relative references, and
|
||||||
// allowing or disallowing remote or local file lookups.
|
// allowing or disallowing remote or local file lookups.
|
||||||
// - https://github.com/pb33f/libopenapi/issues/73
|
// - https://github.com/pb33f/libopenapi/issues/73
|
||||||
type SpecIndexConfig struct {
|
type SpecIndexConfig struct {
|
||||||
// The BaseURL will be the root from which relative references will be resolved from if they can't be found locally.
|
// The BaseURL will be the root from which relative references will be resolved from if they can't be found locally.
|
||||||
//
|
//
|
||||||
// For example:
|
// For example:
|
||||||
// - $ref: somefile.yaml#/components/schemas/SomeSchema
|
// - $ref: somefile.yaml#/components/schemas/SomeSchema
|
||||||
//
|
//
|
||||||
// Might not be found locally, if the file was pulled in from a remote server (a good example is the DigitalOcean API).
|
// Might not be found locally, if the file was pulled in from a remote server (a good example is the DigitalOcean API).
|
||||||
// so by setting a BaseURL, the reference will try to be resolved from the remote server.
|
// so by setting a BaseURL, the reference will try to be resolved from the remote server.
|
||||||
//
|
//
|
||||||
// If our baseURL is set to https://pb33f.io/libopenapi then our reference will try to be resolved from:
|
// If our baseURL is set to https://pb33f.io/libopenapi then our reference will try to be resolved from:
|
||||||
// - $ref: https://pb33f.io/libopenapi/somefile.yaml#/components/schemas/SomeSchema
|
// - $ref: https://pb33f.io/libopenapi/somefile.yaml#/components/schemas/SomeSchema
|
||||||
//
|
//
|
||||||
// More details on relative references can be found in issue #73: https://github.com/pb33f/libopenapi/issues/73
|
// More details on relative references can be found in issue #73: https://github.com/pb33f/libopenapi/issues/73
|
||||||
BaseURL *url.URL // set the Base URL for resolving relative references if the spec is exploded.
|
BaseURL *url.URL // set the Base URL for resolving relative references if the spec is exploded.
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
||||||
// In an earlier version of libopenapi (pre 0.6.0) the index would automatically resolve all references
|
// In an earlier version of libopenapi (pre 0.6.0) the index would automatically resolve all references
|
||||||
// They could have been local, or they could have been remote. This was a problem because it meant
|
// They could have been local, or they could have been remote. This was a problem because it meant
|
||||||
// There was a potential for a remote exploit if a remote reference was malicious. There aren't any known
|
// There was a potential for a remote exploit if a remote reference was malicious. There aren't any known
|
||||||
// exploits, but it's better to be safe than sorry.
|
// exploits, but it's better to be safe than sorry.
|
||||||
//
|
//
|
||||||
// To read more about this, you can find a discussion here: https://github.com/pb33f/libopenapi/pull/64
|
// To read more about this, you can find a discussion here: https://github.com/pb33f/libopenapi/pull/64
|
||||||
AllowRemoteLookup bool // Allow remote lookups for references. Defaults to false
|
AllowRemoteLookup bool // Allow remote lookups for references. Defaults to false
|
||||||
AllowFileLookup bool // Allow file lookups for references. Defaults to false
|
AllowFileLookup bool // Allow file lookups for references. Defaults to false
|
||||||
|
|
||||||
// private fields
|
// ParentIndex allows the index to be created with knowledge of a parent, before being parsed. This allows
|
||||||
seenRemoteSources *syncmap.Map
|
// a breakglass to be used to prevent loops, checking the tree before recursing down.
|
||||||
remoteLock *sync.Mutex
|
ParentIndex *SpecIndex
|
||||||
|
|
||||||
|
// private fields
|
||||||
|
seenRemoteSources *syncmap.Map
|
||||||
|
remoteLock *sync.Mutex
|
||||||
|
uri []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOpenAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
|
// CreateOpenAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
|
||||||
@@ -82,13 +87,13 @@ type SpecIndexConfig struct {
|
|||||||
//
|
//
|
||||||
// The default BasePath is the current working directory.
|
// The default BasePath is the current working directory.
|
||||||
func CreateOpenAPIIndexConfig() *SpecIndexConfig {
|
func CreateOpenAPIIndexConfig() *SpecIndexConfig {
|
||||||
cw, _ := os.Getwd()
|
cw, _ := os.Getwd()
|
||||||
return &SpecIndexConfig{
|
return &SpecIndexConfig{
|
||||||
BasePath: cw,
|
BasePath: cw,
|
||||||
AllowRemoteLookup: true,
|
AllowRemoteLookup: true,
|
||||||
AllowFileLookup: true,
|
AllowFileLookup: true,
|
||||||
seenRemoteSources: &syncmap.Map{},
|
seenRemoteSources: &syncmap.Map{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateClosedAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
|
// CreateClosedAPIIndexConfig is a helper function to create a new SpecIndexConfig with the AllowRemoteLookup and
|
||||||
@@ -96,141 +101,142 @@ func CreateOpenAPIIndexConfig() *SpecIndexConfig {
|
|||||||
//
|
//
|
||||||
// The default BasePath is the current working directory.
|
// The default BasePath is the current working directory.
|
||||||
func CreateClosedAPIIndexConfig() *SpecIndexConfig {
|
func CreateClosedAPIIndexConfig() *SpecIndexConfig {
|
||||||
cw, _ := os.Getwd()
|
cw, _ := os.Getwd()
|
||||||
return &SpecIndexConfig{
|
return &SpecIndexConfig{
|
||||||
BasePath: cw,
|
BasePath: cw,
|
||||||
AllowRemoteLookup: false,
|
AllowRemoteLookup: false,
|
||||||
AllowFileLookup: false,
|
AllowFileLookup: false,
|
||||||
seenRemoteSources: &syncmap.Map{},
|
seenRemoteSources: &syncmap.Map{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpecIndex is a complete pre-computed index of the entire specification. Numbers are pre-calculated and
|
// SpecIndex is a complete pre-computed index of the entire specification. Numbers are pre-calculated and
|
||||||
// quick direct access to paths, operations, tags are all available. No need to walk the entire node tree in rules,
|
// quick direct access to paths, operations, tags are all available. No need to walk the entire node tree in rules,
|
||||||
// everything is pre-walked if you need it.
|
// everything is pre-walked if you need it.
|
||||||
type SpecIndex struct {
|
type SpecIndex struct {
|
||||||
allRefs map[string]*Reference // all (deduplicated) refs
|
allRefs map[string]*Reference // all (deduplicated) refs
|
||||||
rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped.
|
rawSequencedRefs []*Reference // all raw references in sequence as they are scanned, not deduped.
|
||||||
linesWithRefs map[int]bool // lines that link to references.
|
linesWithRefs map[int]bool // lines that link to references.
|
||||||
allMappedRefs map[string]*Reference // these are the located mapped refs
|
allMappedRefs map[string]*Reference // these are the located mapped refs
|
||||||
allMappedRefsSequenced []*ReferenceMapped // sequenced mapped refs
|
allMappedRefsSequenced []*ReferenceMapped // sequenced mapped refs
|
||||||
refsByLine map[string]map[int]bool // every reference and the lines it's referenced from
|
refsByLine map[string]map[int]bool // every reference and the lines it's referenced from
|
||||||
pathRefs map[string]map[string]*Reference // all path references
|
pathRefs map[string]map[string]*Reference // all path references
|
||||||
paramOpRefs map[string]map[string]map[string][]*Reference // params in operations.
|
paramOpRefs map[string]map[string]map[string][]*Reference // params in operations.
|
||||||
paramCompRefs map[string]*Reference // params in components
|
paramCompRefs map[string]*Reference // params in components
|
||||||
paramAllRefs map[string]*Reference // combined components and ops
|
paramAllRefs map[string]*Reference // combined components and ops
|
||||||
paramInlineDuplicateNames map[string][]*Reference // inline params all with the same name
|
paramInlineDuplicateNames map[string][]*Reference // inline params all with the same name
|
||||||
globalTagRefs map[string]*Reference // top level global tags
|
globalTagRefs map[string]*Reference // top level global tags
|
||||||
securitySchemeRefs map[string]*Reference // top level security schemes
|
securitySchemeRefs map[string]*Reference // top level security schemes
|
||||||
requestBodiesRefs map[string]*Reference // top level request bodies
|
requestBodiesRefs map[string]*Reference // top level request bodies
|
||||||
responsesRefs map[string]*Reference // top level responses
|
responsesRefs map[string]*Reference // top level responses
|
||||||
headersRefs map[string]*Reference // top level responses
|
headersRefs map[string]*Reference // top level responses
|
||||||
examplesRefs map[string]*Reference // top level examples
|
examplesRefs map[string]*Reference // top level examples
|
||||||
securityRequirementRefs map[string]map[string][]*Reference // (NOT $ref) but a name based lookup for requirements
|
securityRequirementRefs map[string]map[string][]*Reference // (NOT $ref) but a name based lookup for requirements
|
||||||
callbacksRefs map[string]map[string][]*Reference // all links
|
callbacksRefs map[string]map[string][]*Reference // all links
|
||||||
linksRefs map[string]map[string][]*Reference // all callbacks
|
linksRefs map[string]map[string][]*Reference // all callbacks
|
||||||
operationTagsRefs map[string]map[string][]*Reference // tags found in operations
|
operationTagsRefs map[string]map[string][]*Reference // tags found in operations
|
||||||
operationDescriptionRefs map[string]map[string]*Reference // descriptions in operations.
|
operationDescriptionRefs map[string]map[string]*Reference // descriptions in operations.
|
||||||
operationSummaryRefs map[string]map[string]*Reference // summaries in operations
|
operationSummaryRefs map[string]map[string]*Reference // summaries in operations
|
||||||
callbackRefs map[string]*Reference // top level callback refs
|
callbackRefs map[string]*Reference // top level callback refs
|
||||||
serversRefs []*Reference // all top level server refs
|
serversRefs []*Reference // all top level server refs
|
||||||
rootServersNode *yaml.Node // servers root node
|
rootServersNode *yaml.Node // servers root node
|
||||||
opServersRefs map[string]map[string][]*Reference // all operation level server overrides.
|
opServersRefs map[string]map[string][]*Reference // all operation level server overrides.
|
||||||
polymorphicRefs map[string]*Reference // every reference to a polymorphic ref
|
polymorphicRefs map[string]*Reference // every reference to a polymorphic ref
|
||||||
polymorphicAllOfRefs []*Reference // every reference to 'allOf' references
|
polymorphicAllOfRefs []*Reference // every reference to 'allOf' references
|
||||||
polymorphicOneOfRefs []*Reference // every reference to 'oneOf' references
|
polymorphicOneOfRefs []*Reference // every reference to 'oneOf' references
|
||||||
polymorphicAnyOfRefs []*Reference // every reference to 'anyOf' references
|
polymorphicAnyOfRefs []*Reference // every reference to 'anyOf' references
|
||||||
externalDocumentsRef []*Reference // all external documents in spec
|
externalDocumentsRef []*Reference // all external documents in spec
|
||||||
rootSecurity []*Reference // root security definitions.
|
rootSecurity []*Reference // root security definitions.
|
||||||
rootSecurityNode *yaml.Node // root security node.
|
rootSecurityNode *yaml.Node // root security node.
|
||||||
refsWithSiblings map[string]Reference // references with sibling elements next to them
|
refsWithSiblings map[string]Reference // references with sibling elements next to them
|
||||||
pathRefsLock sync.Mutex // create lock for all refs maps, we want to build data as fast as we can
|
pathRefsLock sync.Mutex // create lock for all refs maps, we want to build data as fast as we can
|
||||||
externalDocumentsCount int // number of externalDocument nodes found
|
externalDocumentsCount int // number of externalDocument nodes found
|
||||||
operationTagsCount int // number of unique tags in operations
|
operationTagsCount int // number of unique tags in operations
|
||||||
globalTagsCount int // number of global tags defined
|
globalTagsCount int // number of global tags defined
|
||||||
totalTagsCount int // number unique tags in spec
|
totalTagsCount int // number unique tags in spec
|
||||||
securitySchemesCount int // security schemes
|
securitySchemesCount int // security schemes
|
||||||
globalRequestBodiesCount int // component request bodies
|
globalRequestBodiesCount int // component request bodies
|
||||||
globalResponsesCount int // component responses
|
globalResponsesCount int // component responses
|
||||||
globalHeadersCount int // component headers
|
globalHeadersCount int // component headers
|
||||||
globalExamplesCount int // component examples
|
globalExamplesCount int // component examples
|
||||||
globalLinksCount int // component links
|
globalLinksCount int // component links
|
||||||
globalCallbacksCount int // component callbacks
|
globalCallbacksCount int // component callbacks
|
||||||
globalCallbacks int // component callbacks.
|
globalCallbacks int // component callbacks.
|
||||||
pathCount int // number of paths
|
pathCount int // number of paths
|
||||||
operationCount int // number of operations
|
operationCount int // number of operations
|
||||||
operationParamCount int // number of params defined in operations
|
operationParamCount int // number of params defined in operations
|
||||||
componentParamCount int // number of params defined in components
|
componentParamCount int // number of params defined in components
|
||||||
componentsInlineParamUniqueCount int // number of inline params with unique names
|
componentsInlineParamUniqueCount int // number of inline params with unique names
|
||||||
componentsInlineParamDuplicateCount int // number of inline params with duplicate names
|
componentsInlineParamDuplicateCount int // number of inline params with duplicate names
|
||||||
schemaCount int // number of schemas
|
schemaCount int // number of schemas
|
||||||
refCount int // total ref count
|
refCount int // total ref count
|
||||||
root *yaml.Node // the root document
|
root *yaml.Node // the root document
|
||||||
pathsNode *yaml.Node // paths node
|
pathsNode *yaml.Node // paths node
|
||||||
tagsNode *yaml.Node // tags node
|
tagsNode *yaml.Node // tags node
|
||||||
componentsNode *yaml.Node // components node
|
componentsNode *yaml.Node // components node
|
||||||
parametersNode *yaml.Node // components/parameters node
|
parametersNode *yaml.Node // components/parameters node
|
||||||
allParametersNode map[string]*Reference // all parameters node
|
allParametersNode map[string]*Reference // all parameters node
|
||||||
allParameters map[string]*Reference // all parameters (components/defs)
|
allParameters map[string]*Reference // all parameters (components/defs)
|
||||||
schemasNode *yaml.Node // components/schemas node
|
schemasNode *yaml.Node // components/schemas node
|
||||||
allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger).
|
allInlineSchemaDefinitions []*Reference // all schemas found in document outside of components (openapi) or definitions (swagger).
|
||||||
allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger).
|
allInlineSchemaObjectDefinitions []*Reference // all schemas that are objects found in document outside of components (openapi) or definitions (swagger).
|
||||||
allComponentSchemaDefinitions map[string]*Reference // all schemas found in components (openapi) or definitions (swagger).
|
allComponentSchemaDefinitions map[string]*Reference // all schemas found in components (openapi) or definitions (swagger).
|
||||||
securitySchemesNode *yaml.Node // components/securitySchemes node
|
securitySchemesNode *yaml.Node // components/securitySchemes node
|
||||||
allSecuritySchemes map[string]*Reference // all security schemes / definitions.
|
allSecuritySchemes map[string]*Reference // all security schemes / definitions.
|
||||||
requestBodiesNode *yaml.Node // components/requestBodies node
|
requestBodiesNode *yaml.Node // components/requestBodies node
|
||||||
allRequestBodies map[string]*Reference // all request bodies
|
allRequestBodies map[string]*Reference // all request bodies
|
||||||
responsesNode *yaml.Node // components/responses node
|
responsesNode *yaml.Node // components/responses node
|
||||||
allResponses map[string]*Reference // all responses
|
allResponses map[string]*Reference // all responses
|
||||||
headersNode *yaml.Node // components/headers node
|
headersNode *yaml.Node // components/headers node
|
||||||
allHeaders map[string]*Reference // all headers
|
allHeaders map[string]*Reference // all headers
|
||||||
examplesNode *yaml.Node // components/examples node
|
examplesNode *yaml.Node // components/examples node
|
||||||
allExamples map[string]*Reference // all components examples
|
allExamples map[string]*Reference // all components examples
|
||||||
linksNode *yaml.Node // components/links node
|
linksNode *yaml.Node // components/links node
|
||||||
allLinks map[string]*Reference // all links
|
allLinks map[string]*Reference // all links
|
||||||
callbacksNode *yaml.Node // components/callbacks node
|
callbacksNode *yaml.Node // components/callbacks node
|
||||||
allCallbacks map[string]*Reference // all components examples
|
allCallbacks map[string]*Reference // all components examples
|
||||||
externalDocumentsNode *yaml.Node // external documents node
|
externalDocumentsNode *yaml.Node // external documents node
|
||||||
allExternalDocuments map[string]*Reference // all external documents
|
allExternalDocuments map[string]*Reference // all external documents
|
||||||
externalSpecIndex map[string]*SpecIndex // create a primary index of all external specs and componentIds
|
externalSpecIndex map[string]*SpecIndex // create a primary index of all external specs and componentIds
|
||||||
refErrors []error // errors when indexing references
|
refErrors []error // errors when indexing references
|
||||||
operationParamErrors []error // errors when indexing parameters
|
operationParamErrors []error // errors when indexing parameters
|
||||||
allDescriptions []*DescriptionReference // every single description found in the spec.
|
allDescriptions []*DescriptionReference // every single description found in the spec.
|
||||||
allSummaries []*DescriptionReference // every single summary found in the spec.
|
allSummaries []*DescriptionReference // every single summary found in the spec.
|
||||||
allEnums []*EnumReference // every single enum found in the spec.
|
allEnums []*EnumReference // every single enum found in the spec.
|
||||||
allObjectsWithProperties []*ObjectReference // every single object with properties found in the spec.
|
allObjectsWithProperties []*ObjectReference // every single object with properties found in the spec.
|
||||||
enumCount int
|
enumCount int
|
||||||
descriptionCount int
|
descriptionCount int
|
||||||
summaryCount int
|
summaryCount int
|
||||||
seenRemoteSources map[string]*yaml.Node
|
seenRemoteSources map[string]*yaml.Node
|
||||||
seenLocalSources map[string]*yaml.Node
|
seenLocalSources map[string]*yaml.Node
|
||||||
refLock sync.Mutex
|
refLock sync.Mutex
|
||||||
sourceLock sync.Mutex
|
sourceLock sync.Mutex
|
||||||
componentLock sync.RWMutex
|
componentLock sync.RWMutex
|
||||||
externalLock sync.RWMutex
|
externalLock sync.RWMutex
|
||||||
errorLock sync.RWMutex
|
errorLock sync.RWMutex
|
||||||
circularReferences []*CircularReferenceResult // only available when the resolver has been used.
|
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.
|
allowCircularReferences bool // decide if you want to error out, or allow circular references, default is false.
|
||||||
relativePath string // relative path of the spec file.
|
relativePath string // relative path of the spec file.
|
||||||
config *SpecIndexConfig // configuration for the index
|
config *SpecIndexConfig // configuration for the index
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
componentIndexChan chan bool
|
componentIndexChan chan bool
|
||||||
polyComponentIndexChan chan bool
|
polyComponentIndexChan chan bool
|
||||||
|
|
||||||
// when things get complex (looking at you digital ocean) then we need to know
|
// when things get complex (looking at you digital ocean) then we need to know
|
||||||
// what we have seen across indexes, so we need to be able to travel back up to the root
|
// what we have seen across indexes, so we need to be able to travel back up to the root
|
||||||
// cto avoid re-downloading sources.
|
// cto avoid re-downloading sources.
|
||||||
parentIndex *SpecIndex
|
parentIndex *SpecIndex
|
||||||
children []*SpecIndex
|
uri []string
|
||||||
|
children []*SpecIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (index *SpecIndex) AddChild(child *SpecIndex) {
|
func (index *SpecIndex) AddChild(child *SpecIndex) {
|
||||||
index.children = append(index.children, child)
|
index.children = append(index.children, child)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChildren returns the children of this index.
|
// GetChildren returns the children of this index.
|
||||||
func (index *SpecIndex) GetChildren() []*SpecIndex {
|
func (index *SpecIndex) GetChildren() []*SpecIndex {
|
||||||
return index.children
|
return index.children
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExternalLookupFunction is for lookup functions that take a JSONSchema reference and tries to find that node in the
|
// ExternalLookupFunction is for lookup functions that take a JSONSchema reference and tries to find that node in the
|
||||||
@@ -239,35 +245,35 @@ type ExternalLookupFunction func(id string) (foundNode *yaml.Node, rootNode *yam
|
|||||||
|
|
||||||
// IndexingError holds data about something that went wrong during indexing.
|
// IndexingError holds data about something that went wrong during indexing.
|
||||||
type IndexingError struct {
|
type IndexingError struct {
|
||||||
Err error
|
Err error
|
||||||
Node *yaml.Node
|
Node *yaml.Node
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IndexingError) Error() string {
|
func (i *IndexingError) Error() string {
|
||||||
return i.Err.Error()
|
return i.Err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DescriptionReference holds data about a description that was found and where it was found.
|
// DescriptionReference holds data about a description that was found and where it was found.
|
||||||
type DescriptionReference struct {
|
type DescriptionReference struct {
|
||||||
Content string
|
Content string
|
||||||
Path string
|
Path string
|
||||||
Node *yaml.Node
|
Node *yaml.Node
|
||||||
IsSummary bool
|
IsSummary bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnumReference struct {
|
type EnumReference struct {
|
||||||
Node *yaml.Node
|
Node *yaml.Node
|
||||||
Type *yaml.Node
|
Type *yaml.Node
|
||||||
Path string
|
Path string
|
||||||
SchemaNode *yaml.Node
|
SchemaNode *yaml.Node
|
||||||
ParentNode *yaml.Node
|
ParentNode *yaml.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
type ObjectReference struct {
|
type ObjectReference struct {
|
||||||
Node *yaml.Node
|
Node *yaml.Node
|
||||||
Path string
|
Path string
|
||||||
ParentNode *yaml.Node
|
ParentNode *yaml.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
var methodTypes = []string{"get", "post", "put", "patch", "options", "head", "delete"}
|
var methodTypes = []string{"get", "post", "put", "patch", "options", "head", "delete"}
|
||||||
|
|||||||
@@ -3,37 +3,32 @@
|
|||||||
|
|
||||||
package index
|
package index
|
||||||
|
|
||||||
import "gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
// SearchIndexForReference searches the index for a reference, first looking through the mapped references
|
// SearchIndexForReference searches the index for a reference, first looking through the mapped references
|
||||||
// and then externalSpecIndex for a match. If no match is found, it will recursively search the child indexes
|
// and then externalSpecIndex for a match. If no match is found, it will recursively search the child indexes
|
||||||
// extracted when parsing the OpenAPI Spec.
|
// extracted when parsing the OpenAPI Spec.
|
||||||
func (index *SpecIndex) SearchIndexForReference(ref string) []*Reference {
|
func (index *SpecIndex) SearchIndexForReference(ref string) []*Reference {
|
||||||
if r, ok := index.allMappedRefs[ref]; ok {
|
if r, ok := index.allMappedRefs[ref]; ok {
|
||||||
if r.Node.Kind == yaml.DocumentNode {
|
return []*Reference{r}
|
||||||
// the reference is an entire document, so we need to dig down a level and rewire the reference.
|
}
|
||||||
r.Node = r.Node.Content[0]
|
for c := range index.children {
|
||||||
}
|
found := goFindMeSomething(index.children[c], ref)
|
||||||
return []*Reference{r}
|
if found != nil {
|
||||||
}
|
return found
|
||||||
if r, ok := index.externalSpecIndex[ref]; ok {
|
}
|
||||||
return []*Reference{
|
}
|
||||||
{
|
return nil
|
||||||
Node: r.root.Content[0],
|
}
|
||||||
Name: ref,
|
|
||||||
Definition: ref,
|
func (index *SpecIndex) SearchAncestryForSeenURI(uri string) *SpecIndex {
|
||||||
},
|
if index.parentIndex == nil {
|
||||||
}
|
return nil
|
||||||
}
|
}
|
||||||
for c := range index.children {
|
if index.uri[0] != uri {
|
||||||
found := goFindMeSomething(index.children[c], ref)
|
return index.parentIndex.SearchAncestryForSeenURI(uri)
|
||||||
if found != nil {
|
}
|
||||||
return found
|
return index
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func goFindMeSomething(i *SpecIndex, ref string) []*Reference {
|
func goFindMeSomething(i *SpecIndex, ref string) []*Reference {
|
||||||
return i.SearchIndexForReference(ref)
|
return i.SearchIndexForReference(ref)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,50 +4,50 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"io/ioutil"
|
"net/url"
|
||||||
"net/url"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSpecIndex_SearchIndexForReference(t *testing.T) {
|
func TestSpecIndex_SearchIndexForReference(t *testing.T) {
|
||||||
petstore, _ := ioutil.ReadFile("../test_specs/petstorev3.json")
|
petstore, _ := os.ReadFile("../test_specs/petstorev3.json")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
_ = yaml.Unmarshal(petstore, &rootNode)
|
_ = yaml.Unmarshal(petstore, &rootNode)
|
||||||
|
|
||||||
c := CreateOpenAPIIndexConfig()
|
c := CreateOpenAPIIndexConfig()
|
||||||
idx := NewSpecIndexWithConfig(&rootNode, c)
|
idx := NewSpecIndexWithConfig(&rootNode, c)
|
||||||
|
|
||||||
ref := idx.SearchIndexForReference("#/components/schemas/Pet")
|
ref := idx.SearchIndexForReference("#/components/schemas/Pet")
|
||||||
assert.NotNil(t, ref)
|
assert.NotNil(t, ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpecIndex_SearchIndexForReference_ExternalSpecs(t *testing.T) {
|
func TestSpecIndex_SearchIndexForReference_ExternalSpecs(t *testing.T) {
|
||||||
|
|
||||||
// load up an index with lots of references
|
// load up an index with lots of references
|
||||||
petstore, _ := ioutil.ReadFile("../test_specs/digitalocean.yaml")
|
petstore, _ := os.ReadFile("../test_specs/digitalocean.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
_ = yaml.Unmarshal(petstore, &rootNode)
|
_ = yaml.Unmarshal(petstore, &rootNode)
|
||||||
|
|
||||||
c := CreateOpenAPIIndexConfig()
|
c := CreateOpenAPIIndexConfig()
|
||||||
c.BaseURL, _ = url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")
|
c.BaseURL, _ = url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")
|
||||||
idx := NewSpecIndexWithConfig(&rootNode, c)
|
idx := NewSpecIndexWithConfig(&rootNode, c)
|
||||||
|
|
||||||
ref := idx.SearchIndexForReference("resources/apps/apps_list_instanceSizes.yml")
|
ref := idx.SearchIndexForReference("resources/apps/apps_list_instanceSizes.yml")
|
||||||
assert.NotNil(t, ref)
|
assert.NotNil(t, ref)
|
||||||
assert.Equal(t, "operationId", ref[0].Node.Content[0].Value)
|
assert.Equal(t, "operationId", ref[0].Node.Content[0].Value)
|
||||||
|
|
||||||
ref = idx.SearchIndexForReference("examples/ruby/domains_create.yml")
|
ref = idx.SearchIndexForReference("examples/ruby/domains_create.yml")
|
||||||
assert.NotNil(t, ref)
|
assert.NotNil(t, ref)
|
||||||
assert.Equal(t, "lang", ref[0].Node.Content[0].Value)
|
assert.Equal(t, "lang", ref[0].Node.Content[0].Value)
|
||||||
|
|
||||||
ref = idx.SearchIndexForReference("../../shared/responses/server_error.yml")
|
ref = idx.SearchIndexForReference("../../shared/responses/server_error.yml")
|
||||||
assert.NotNil(t, ref)
|
assert.NotNil(t, ref)
|
||||||
assert.Equal(t, "description", ref[0].Node.Content[0].Value)
|
assert.Equal(t, "description", ref[0].Node.Content[0].Value)
|
||||||
|
|
||||||
ref = idx.SearchIndexForReference("../models/options.yml")
|
ref = idx.SearchIndexForReference("../models/options.yml")
|
||||||
assert.NotNil(t, ref)
|
assert.NotNil(t, ref)
|
||||||
assert.Equal(t, "kubernetes_options", ref[0].Node.Content[0].Value)
|
assert.Equal(t, "kubernetes_options", ref[0].Node.Content[0].Value)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ func NewSpecIndexWithConfig(rootNode *yaml.Node, config *SpecIndexConfig) *SpecI
|
|||||||
}
|
}
|
||||||
config.remoteLock = &sync.Mutex{}
|
config.remoteLock = &sync.Mutex{}
|
||||||
index.config = config
|
index.config = config
|
||||||
|
index.parentIndex = config.ParentIndex
|
||||||
|
index.uri = config.uri
|
||||||
if rootNode == nil || len(rootNode.Content) <= 0 {
|
if rootNode == nil || len(rootNode.Content) <= 0 {
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
@@ -47,7 +49,7 @@ func NewSpecIndexWithConfig(rootNode *yaml.Node, config *SpecIndexConfig) *SpecI
|
|||||||
// defaults to allowing remote references and file references. This is a potential security risk and should be controlled by
|
// defaults to allowing remote references and file references. This is a potential security risk and should be controlled by
|
||||||
// providing a SpecIndexConfig that explicitly sets the AllowRemoteLookup and AllowFileLookup to true.
|
// providing a SpecIndexConfig that explicitly sets the AllowRemoteLookup and AllowFileLookup to true.
|
||||||
// This function also does not support specifications with relative references that may not exist locally.
|
// This function also does not support specifications with relative references that may not exist locally.
|
||||||
// - https://github.com/pb33f/libopenapi/issues/73
|
// - https://github.com/pb33f/libopenapi/issues/73
|
||||||
func NewSpecIndex(rootNode *yaml.Node) *SpecIndex {
|
func NewSpecIndex(rootNode *yaml.Node) *SpecIndex {
|
||||||
index := new(SpecIndex)
|
index := new(SpecIndex)
|
||||||
index.config = CreateOpenAPIIndexConfig()
|
index.config = CreateOpenAPIIndexConfig()
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func TestSpecIndex_Asana(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSpecIndex_DigitalOcean(t *testing.T) {
|
func TestSpecIndex_DigitalOcean(t *testing.T) {
|
||||||
do, _ := ioutil.ReadFile("../test_specs/digitalocean.yaml")
|
do, _ := os.ReadFile("../test_specs/digitalocean.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
_ = yaml.Unmarshal(do, &rootNode)
|
_ = yaml.Unmarshal(do, &rootNode)
|
||||||
|
|
||||||
@@ -603,7 +603,6 @@ func TestSpecIndex_FindComponenth(t *testing.T) {
|
|||||||
assert.Nil(t, index.FindComponent("I-do-not-exist", nil))
|
assert.Nil(t, index.FindComponent("I-do-not-exist", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestSpecIndex_TestPathsNodeAsArray(t *testing.T) {
|
func TestSpecIndex_TestPathsNodeAsArray(t *testing.T) {
|
||||||
yml := `components:
|
yml := `components:
|
||||||
schemas:
|
schemas:
|
||||||
@@ -633,21 +632,21 @@ func TestSpecIndex_lookupRemoteReference_SeenSourceSimulation_BadFind(t *testing
|
|||||||
index := new(SpecIndex)
|
index := new(SpecIndex)
|
||||||
index.seenRemoteSources = make(map[string]*yaml.Node)
|
index.seenRemoteSources = make(map[string]*yaml.Node)
|
||||||
index.seenRemoteSources["https://no-hope-for-a-dope.com"] = &yaml.Node{}
|
index.seenRemoteSources["https://no-hope-for-a-dope.com"] = &yaml.Node{}
|
||||||
a, b, err := index.lookupRemoteReference("https://no-hope-for-a-dope.com#/hey")
|
a, b, err := index.lookupRemoteReference("https://no-hope-for-a-dope.com#/hey")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, a)
|
assert.Nil(t, a)
|
||||||
assert.Nil(t, b)
|
assert.Nil(t, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discovered in issue https://github.com/pb33f/libopenapi/issues/37
|
// Discovered in issue https://github.com/pb33f/libopenapi/issues/37
|
||||||
func TestSpecIndex_lookupRemoteReference_NoComponent(t *testing.T) {
|
func TestSpecIndex_lookupRemoteReference_NoComponent(t *testing.T) {
|
||||||
index := new(SpecIndex)
|
index := new(SpecIndex)
|
||||||
index.seenRemoteSources = make(map[string]*yaml.Node)
|
index.seenRemoteSources = make(map[string]*yaml.Node)
|
||||||
index.seenRemoteSources["https://api.rest.sh/schemas/ErrorModel.json"] = &yaml.Node{}
|
index.seenRemoteSources["https://api.rest.sh/schemas/ErrorModel.json"] = &yaml.Node{}
|
||||||
a, b, err := index.lookupRemoteReference("https://api.rest.sh/schemas/ErrorModel.json")
|
a, b, err := index.lookupRemoteReference("https://api.rest.sh/schemas/ErrorModel.json")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, a)
|
assert.NotNil(t, a)
|
||||||
assert.NotNil(t, b)
|
assert.NotNil(t, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discovered in issue https://github.com/daveshanley/vacuum/issues/225
|
// Discovered in issue https://github.com/daveshanley/vacuum/issues/225
|
||||||
@@ -669,47 +668,47 @@ func TestSpecIndex_lookupFileReference_NoComponent(t *testing.T) {
|
|||||||
|
|
||||||
func TestSpecIndex_CheckBadURLRef(t *testing.T) {
|
func TestSpecIndex_CheckBadURLRef(t *testing.T) {
|
||||||
|
|
||||||
yml := `openapi: 3.1.0
|
yml := `openapi: 3.1.0
|
||||||
paths:
|
paths:
|
||||||
/cakes:
|
/cakes:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: 'httpsss://badurl'`
|
- $ref: 'httpsss://badurl'`
|
||||||
|
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
_ = yaml.Unmarshal([]byte(yml), &rootNode)
|
_ = yaml.Unmarshal([]byte(yml), &rootNode)
|
||||||
|
|
||||||
index := NewSpecIndexWithConfig(&rootNode, CreateOpenAPIIndexConfig())
|
index := NewSpecIndexWithConfig(&rootNode, CreateOpenAPIIndexConfig())
|
||||||
|
|
||||||
assert.Len(t, index.refErrors, 2)
|
assert.Len(t, index.refErrors, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpecIndex_CheckBadURLRefNoRemoteAllowed(t *testing.T) {
|
func TestSpecIndex_CheckBadURLRefNoRemoteAllowed(t *testing.T) {
|
||||||
|
|
||||||
yml := `openapi: 3.1.0
|
yml := `openapi: 3.1.0
|
||||||
paths:
|
paths:
|
||||||
/cakes:
|
/cakes:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: 'httpsss://badurl'`
|
- $ref: 'httpsss://badurl'`
|
||||||
|
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
_ = yaml.Unmarshal([]byte(yml), &rootNode)
|
_ = yaml.Unmarshal([]byte(yml), &rootNode)
|
||||||
|
|
||||||
c := CreateClosedAPIIndexConfig()
|
c := CreateClosedAPIIndexConfig()
|
||||||
idx := NewSpecIndexWithConfig(&rootNode, c)
|
idx := NewSpecIndexWithConfig(&rootNode, c)
|
||||||
|
|
||||||
assert.Len(t, idx.refErrors, 2)
|
assert.Len(t, idx.refErrors, 2)
|
||||||
assert.Equal(t, "remote lookups are not permitted, "+
|
assert.Equal(t, "remote lookups are not permitted, "+
|
||||||
"please set AllowRemoteLookup to true in the configuration", idx.refErrors[0].Error())
|
"please set AllowRemoteLookup to true in the configuration", idx.refErrors[0].Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpecIndex_CheckIndexDiscoversNoComponentLocalFileReference(t *testing.T) {
|
func TestSpecIndex_CheckIndexDiscoversNoComponentLocalFileReference(t *testing.T) {
|
||||||
|
|
||||||
_ = ioutil.WriteFile("coffee-time.yaml", []byte("name: time for coffee"), 0o664)
|
_ = ioutil.WriteFile("coffee-time.yaml", []byte("name: time for coffee"), 0o664)
|
||||||
defer os.Remove("coffee-time.yaml")
|
defer os.Remove("coffee-time.yaml")
|
||||||
|
|
||||||
yml := `openapi: 3.0.3
|
yml := `openapi: 3.0.3
|
||||||
paths:
|
paths:
|
||||||
/cakes:
|
/cakes:
|
||||||
post:
|
post:
|
||||||
|
|||||||
@@ -1,170 +1,170 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pb33f/libopenapi/index"
|
"github.com/pb33f/libopenapi/index"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewResolver(t *testing.T) {
|
func TestNewResolver(t *testing.T) {
|
||||||
assert.Nil(t, NewResolver(nil))
|
assert.Nil(t, NewResolver(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Benchmark_ResolveDocumentStripe(b *testing.B) {
|
func Benchmark_ResolveDocumentStripe(b *testing.B) {
|
||||||
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(stripe, &rootNode)
|
yaml.Unmarshal(stripe, &rootNode)
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
resolver.Resolve()
|
resolver.Resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_CircularSpec(t *testing.T) {
|
func TestResolver_ResolveComponents_CircularSpec(t *testing.T) {
|
||||||
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
|
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(circular, &rootNode)
|
yaml.Unmarshal(circular, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.Resolve()
|
circ := resolver.Resolve()
|
||||||
assert.Len(t, circ, 3)
|
assert.Len(t, circ, 3)
|
||||||
|
|
||||||
_, err := yaml.Marshal(resolver.resolvedRoot)
|
_, err := yaml.Marshal(resolver.resolvedRoot)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_CheckForCircularReferences(t *testing.T) {
|
func TestResolver_CheckForCircularReferences(t *testing.T) {
|
||||||
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
|
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(circular, &rootNode)
|
yaml.Unmarshal(circular, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.CheckForCircularReferences()
|
circ := resolver.CheckForCircularReferences()
|
||||||
assert.Len(t, circ, 3)
|
assert.Len(t, circ, 3)
|
||||||
assert.Len(t, resolver.GetResolvingErrors(), 3)
|
assert.Len(t, resolver.GetResolvingErrors(), 3)
|
||||||
assert.Len(t, resolver.GetCircularErrors(), 3)
|
assert.Len(t, resolver.GetCircularErrors(), 3)
|
||||||
|
|
||||||
_, err := yaml.Marshal(resolver.resolvedRoot)
|
_, err := yaml.Marshal(resolver.resolvedRoot)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_CheckForCircularReferences_DigitalOcean(t *testing.T) {
|
func TestResolver_CheckForCircularReferences_DigitalOcean(t *testing.T) {
|
||||||
circular, _ := ioutil.ReadFile("../test_specs/digitalocean.yaml")
|
circular, _ := ioutil.ReadFile("../test_specs/digitalocean.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(circular, &rootNode)
|
yaml.Unmarshal(circular, &rootNode)
|
||||||
|
|
||||||
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")
|
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")
|
||||||
|
|
||||||
index := index.NewSpecIndexWithConfig(&rootNode, &index.SpecIndexConfig{
|
index := index.NewSpecIndexWithConfig(&rootNode, &index.SpecIndexConfig{
|
||||||
AllowRemoteLookup: true,
|
AllowRemoteLookup: true,
|
||||||
AllowFileLookup: true,
|
AllowFileLookup: true,
|
||||||
BaseURL: baseURL,
|
BaseURL: baseURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.CheckForCircularReferences()
|
circ := resolver.CheckForCircularReferences()
|
||||||
assert.Len(t, circ, 0)
|
assert.Len(t, circ, 0)
|
||||||
assert.Len(t, resolver.GetResolvingErrors(), 0)
|
assert.Len(t, resolver.GetResolvingErrors(), 0)
|
||||||
assert.Len(t, resolver.GetCircularErrors(), 0)
|
assert.Len(t, resolver.GetCircularErrors(), 0)
|
||||||
|
|
||||||
_, err := yaml.Marshal(resolver.resolvedRoot)
|
_, err := yaml.Marshal(resolver.resolvedRoot)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_CircularReferencesRequiredValid(t *testing.T) {
|
func TestResolver_CircularReferencesRequiredValid(t *testing.T) {
|
||||||
circular, _ := ioutil.ReadFile("../test_specs/swagger-valid-recursive-model.yaml")
|
circular, _ := ioutil.ReadFile("../test_specs/swagger-valid-recursive-model.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(circular, &rootNode)
|
yaml.Unmarshal(circular, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.CheckForCircularReferences()
|
circ := resolver.CheckForCircularReferences()
|
||||||
assert.Len(t, circ, 0)
|
assert.Len(t, circ, 0)
|
||||||
|
|
||||||
_, err := yaml.Marshal(resolver.resolvedRoot)
|
_, err := yaml.Marshal(resolver.resolvedRoot)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_CircularReferencesRequiredInvalid(t *testing.T) {
|
func TestResolver_CircularReferencesRequiredInvalid(t *testing.T) {
|
||||||
circular, _ := ioutil.ReadFile("../test_specs/swagger-invalid-recursive-model.yaml")
|
circular, _ := ioutil.ReadFile("../test_specs/swagger-invalid-recursive-model.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(circular, &rootNode)
|
yaml.Unmarshal(circular, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.CheckForCircularReferences()
|
circ := resolver.CheckForCircularReferences()
|
||||||
assert.Len(t, circ, 2)
|
assert.Len(t, circ, 2)
|
||||||
|
|
||||||
_, err := yaml.Marshal(resolver.resolvedRoot)
|
_, err := yaml.Marshal(resolver.resolvedRoot)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_DeepJourney(t *testing.T) {
|
func TestResolver_DeepJourney(t *testing.T) {
|
||||||
var journey []*index.Reference
|
var journey []*index.Reference
|
||||||
for f := 0; f < 200; f++ {
|
for f := 0; f < 200; f++ {
|
||||||
journey = append(journey, nil)
|
journey = append(journey, nil)
|
||||||
}
|
}
|
||||||
index := index.NewSpecIndex(nil)
|
index := index.NewSpecIndex(nil)
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.Nil(t, resolver.extractRelatives(nil, nil, journey, false))
|
assert.Nil(t, resolver.extractRelatives(nil, nil, journey, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_Stripe(t *testing.T) {
|
func TestResolver_ResolveComponents_Stripe(t *testing.T) {
|
||||||
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(stripe, &rootNode)
|
yaml.Unmarshal(stripe, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.Resolve()
|
circ := resolver.Resolve()
|
||||||
assert.Len(t, circ, 3)
|
assert.Len(t, circ, 3)
|
||||||
|
|
||||||
assert.Len(t, resolver.GetNonPolymorphicCircularErrors(), 3)
|
assert.Len(t, resolver.GetNonPolymorphicCircularErrors(), 3)
|
||||||
assert.Len(t, resolver.GetPolymorphicCircularErrors(), 0)
|
assert.Len(t, resolver.GetPolymorphicCircularErrors(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_BurgerShop(t *testing.T) {
|
func TestResolver_ResolveComponents_BurgerShop(t *testing.T) {
|
||||||
mixedref, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
|
mixedref, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(mixedref, &rootNode)
|
yaml.Unmarshal(mixedref, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.Resolve()
|
circ := resolver.Resolve()
|
||||||
assert.Len(t, circ, 0)
|
assert.Len(t, circ, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_PolyNonCircRef(t *testing.T) {
|
func TestResolver_ResolveComponents_PolyNonCircRef(t *testing.T) {
|
||||||
yml := `paths:
|
yml := `paths:
|
||||||
/hey:
|
/hey:
|
||||||
get:
|
get:
|
||||||
responses:
|
responses:
|
||||||
@@ -184,20 +184,20 @@ components:
|
|||||||
tea:
|
tea:
|
||||||
description: tea`
|
description: tea`
|
||||||
|
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal([]byte(yml), &rootNode)
|
yaml.Unmarshal([]byte(yml), &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.CheckForCircularReferences()
|
circ := resolver.CheckForCircularReferences()
|
||||||
assert.Len(t, circ, 0)
|
assert.Len(t, circ, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_PolyCircRef(t *testing.T) {
|
func TestResolver_ResolveComponents_PolyCircRef(t *testing.T) {
|
||||||
yml := `openapi: 3.1.0
|
yml := `openapi: 3.1.0
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
cheese:
|
cheese:
|
||||||
@@ -211,24 +211,24 @@ components:
|
|||||||
tea:
|
tea:
|
||||||
description: tea`
|
description: tea`
|
||||||
|
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal([]byte(yml), &rootNode)
|
yaml.Unmarshal([]byte(yml), &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
_ = resolver.CheckForCircularReferences()
|
_ = resolver.CheckForCircularReferences()
|
||||||
resolver.circularReferences[0].IsInfiniteLoop = true // override
|
resolver.circularReferences[0].IsInfiniteLoop = true // override
|
||||||
assert.Len(t, index.GetCircularReferences(), 1)
|
assert.Len(t, index.GetCircularReferences(), 1)
|
||||||
assert.Len(t, resolver.GetPolymorphicCircularErrors(), 1)
|
assert.Len(t, resolver.GetPolymorphicCircularErrors(), 1)
|
||||||
assert.Equal(t, 2, index.GetCircularReferences()[0].LoopIndex)
|
assert.Equal(t, 2, index.GetCircularReferences()[0].LoopIndex)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_Missing(t *testing.T) {
|
func TestResolver_ResolveComponents_Missing(t *testing.T) {
|
||||||
yml := `paths:
|
yml := `paths:
|
||||||
/hey:
|
/hey:
|
||||||
get:
|
get:
|
||||||
responses:
|
responses:
|
||||||
@@ -247,93 +247,95 @@ components:
|
|||||||
butter:
|
butter:
|
||||||
$ref: 'go home, I am drunk'`
|
$ref: 'go home, I am drunk'`
|
||||||
|
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal([]byte(yml), &rootNode)
|
yaml.Unmarshal([]byte(yml), &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
err := resolver.Resolve()
|
err := resolver.Resolve()
|
||||||
assert.Len(t, err, 1)
|
assert.Len(t, err, 1)
|
||||||
assert.Equal(t, "cannot resolve reference `go home, I am drunk`, it's missing: $go home, I am drunk [18:11]", err[0].Error())
|
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_ResolveComponents_MixedRef(t *testing.T) {
|
func TestResolver_ResolveComponents_MixedRef(t *testing.T) {
|
||||||
mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml")
|
mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(mixedref, &rootNode)
|
yaml.Unmarshal(mixedref, &rootNode)
|
||||||
|
|
||||||
b := index.CreateOpenAPIIndexConfig()
|
b := index.CreateOpenAPIIndexConfig()
|
||||||
idx := index.NewSpecIndexWithConfig(&rootNode, b)
|
idx := index.NewSpecIndexWithConfig(&rootNode, b)
|
||||||
|
|
||||||
resolver := NewResolver(idx)
|
resolver := NewResolver(idx)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.Resolve()
|
circ := resolver.Resolve()
|
||||||
assert.Len(t, circ, 0)
|
assert.Len(t, circ, 0)
|
||||||
assert.Equal(t, 5, resolver.GetIndexesVisited())
|
assert.Equal(t, 5, resolver.GetIndexesVisited())
|
||||||
assert.Equal(t, 209, resolver.GetRelativesSeen())
|
|
||||||
assert.Equal(t, 35, resolver.GetJourneysTaken())
|
// in v0.8.2 a new check was added when indexing, to prevent re-indexing the same file multiple times.
|
||||||
assert.Equal(t, 62, resolver.GetReferenceVisited())
|
assert.Equal(t, 191, resolver.GetRelativesSeen())
|
||||||
|
assert.Equal(t, 35, resolver.GetJourneysTaken())
|
||||||
|
assert.Equal(t, 62, resolver.GetReferenceVisited())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolver_ResolveComponents_k8s(t *testing.T) {
|
func TestResolver_ResolveComponents_k8s(t *testing.T) {
|
||||||
k8s, _ := ioutil.ReadFile("../test_specs/k8s.json")
|
k8s, _ := ioutil.ReadFile("../test_specs/k8s.json")
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
yaml.Unmarshal(k8s, &rootNode)
|
yaml.Unmarshal(k8s, &rootNode)
|
||||||
|
|
||||||
index := index.NewSpecIndex(&rootNode)
|
index := index.NewSpecIndex(&rootNode)
|
||||||
|
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
assert.NotNil(t, resolver)
|
assert.NotNil(t, resolver)
|
||||||
|
|
||||||
circ := resolver.Resolve()
|
circ := resolver.Resolve()
|
||||||
assert.Len(t, circ, 0)
|
assert.Len(t, circ, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example of how to resolve the Stripe OpenAPI specification, and check for circular reference errors
|
// Example of how to resolve the Stripe OpenAPI specification, and check for circular reference errors
|
||||||
func ExampleNewResolver() {
|
func ExampleNewResolver() {
|
||||||
// create a yaml.Node reference as a root node.
|
// create a yaml.Node reference as a root node.
|
||||||
var rootNode yaml.Node
|
var rootNode yaml.Node
|
||||||
|
|
||||||
// load in the Stripe OpenAPI spec (lots of polymorphic complexity in here)
|
// load in the Stripe OpenAPI spec (lots of polymorphic complexity in here)
|
||||||
stripeBytes, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
stripeBytes, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
|
||||||
|
|
||||||
// unmarshal bytes into our rootNode.
|
// unmarshal bytes into our rootNode.
|
||||||
_ = yaml.Unmarshal(stripeBytes, &rootNode)
|
_ = yaml.Unmarshal(stripeBytes, &rootNode)
|
||||||
|
|
||||||
// create a new spec index (resolver depends on it)
|
// create a new spec index (resolver depends on it)
|
||||||
indexConfig := index.CreateClosedAPIIndexConfig()
|
indexConfig := index.CreateClosedAPIIndexConfig()
|
||||||
index := index.NewSpecIndexWithConfig(&rootNode, indexConfig)
|
index := index.NewSpecIndexWithConfig(&rootNode, indexConfig)
|
||||||
|
|
||||||
// create a new resolver using the index.
|
// create a new resolver using the index.
|
||||||
resolver := NewResolver(index)
|
resolver := NewResolver(index)
|
||||||
|
|
||||||
// resolve the document, if there are circular reference errors, they are returned/
|
// 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
|
// WARNING: this is a destructive action and the rootNode will be PERMANENTLY altered and cannot be unresolved
|
||||||
circularErrors := resolver.Resolve()
|
circularErrors := resolver.Resolve()
|
||||||
|
|
||||||
// The Stripe API has a bunch of circular reference problems, mainly from polymorphism.
|
// The Stripe API has a bunch of circular reference problems, mainly from polymorphism.
|
||||||
// So let's print them out.
|
// So let's print them out.
|
||||||
//
|
//
|
||||||
fmt.Printf("There are %d circular reference errors, %d of them are polymorphic errors, %d are not",
|
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()))
|
len(circularErrors), len(resolver.GetPolymorphicCircularErrors()), len(resolver.GetNonPolymorphicCircularErrors()))
|
||||||
// Output: There are 3 circular reference errors, 0 of them are polymorphic errors, 3 are not
|
// Output: There are 3 circular reference errors, 0 of them are polymorphic errors, 3 are not
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleResolvingError() {
|
func ExampleResolvingError() {
|
||||||
re := ResolvingError{
|
re := ResolvingError{
|
||||||
ErrorRef: errors.New("Je suis une erreur"),
|
ErrorRef: errors.New("Je suis une erreur"),
|
||||||
Node: &yaml.Node{
|
Node: &yaml.Node{
|
||||||
Line: 5,
|
Line: 5,
|
||||||
Column: 21,
|
Column: 21,
|
||||||
},
|
},
|
||||||
Path: "#/definitions/JeSuisUneErreur",
|
Path: "#/definitions/JeSuisUneErreur",
|
||||||
CircularReference: &index.CircularReferenceResult{},
|
CircularReference: &index.CircularReferenceResult{},
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s", re.Error())
|
fmt.Printf("%s", re.Error())
|
||||||
// Output: Je suis une erreur: #/definitions/JeSuisUneErreur [5:21]
|
// Output: Je suis une erreur: #/definitions/JeSuisUneErreur [5:21]
|
||||||
}
|
}
|
||||||
|
|||||||
33
test_specs/first.yaml
Normal file
33
test_specs/first.yaml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: title
|
||||||
|
description: description
|
||||||
|
version: 0.0.0
|
||||||
|
|
||||||
|
paths:
|
||||||
|
|
||||||
|
/items:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- items
|
||||||
|
summary: summary
|
||||||
|
description: description
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
title: Schema
|
||||||
|
description: description
|
||||||
|
type: object
|
||||||
|
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
title: first title
|
||||||
|
description: first description
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
second:
|
||||||
|
$ref: "second.yaml"
|
||||||
31
test_specs/second.yaml
Normal file
31
test_specs/second.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
title: second doc title
|
||||||
|
description: second doc description
|
||||||
|
type: object
|
||||||
|
|
||||||
|
additionalProperties: false
|
||||||
|
|
||||||
|
properties:
|
||||||
|
|
||||||
|
property1:
|
||||||
|
title: title
|
||||||
|
description: property 1 description
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
title: item
|
||||||
|
description: third description
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
details:
|
||||||
|
$ref: "third.yaml"
|
||||||
|
|
||||||
|
property2:
|
||||||
|
title: title
|
||||||
|
description: property 2 description
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
property:
|
||||||
|
title: title
|
||||||
|
description: tasty description
|
||||||
|
type: integer
|
||||||
15
test_specs/third.yaml
Normal file
15
test_specs/third.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
title: third doc title
|
||||||
|
description: third doc description
|
||||||
|
type: object
|
||||||
|
|
||||||
|
additionalProperties: false
|
||||||
|
maxProperties: 1
|
||||||
|
|
||||||
|
properties:
|
||||||
|
|
||||||
|
property:
|
||||||
|
title: title of third prop in third doc
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
statistics:
|
||||||
|
$ref: 'second.yaml#/properties/property2'
|
||||||
884
utils/utils.go
884
utils/utils.go
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user