mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-06 12:37:49 +00:00
The depth was being artificially inflated with highly complex recursive designs, this fixes the depth counter permanently. Signed-off-by: quobix <dave@quobix.com>
892 lines
27 KiB
Go
892 lines
27 KiB
Go
// Copyright 2022 Dave Shanley / Quobix
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package index
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/pb33f/libopenapi/utils"
|
|
"golang.org/x/exp/slices"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// ResolvingError represents an issue the resolver had trying to stitch the tree together.
|
|
type ResolvingError struct {
|
|
// ErrorRef is the error thrown by the resolver
|
|
ErrorRef error
|
|
|
|
// Node is the *yaml.Node reference that contains the resolving error
|
|
Node *yaml.Node
|
|
|
|
// Path is the shortened journey taken by the resolver
|
|
Path string
|
|
|
|
// CircularReference is set if the error is a reference to the circular reference.
|
|
CircularReference *CircularReferenceResult
|
|
}
|
|
|
|
func (r *ResolvingError) Error() string {
|
|
errs := utils.UnwrapErrors(r.ErrorRef)
|
|
var msgs []string
|
|
for _, e := range errs {
|
|
var idxErr *IndexingError
|
|
if errors.As(e, &idxErr) {
|
|
msgs = append(msgs, fmt.Sprintf("%s: %s [%d:%d]", idxErr.Error(),
|
|
idxErr.Path, idxErr.Node.Line, idxErr.Node.Column))
|
|
} else {
|
|
var l, c int
|
|
if r.Node != nil {
|
|
l = r.Node.Line
|
|
c = r.Node.Column
|
|
}
|
|
msgs = append(msgs, fmt.Sprintf("%s: %s [%d:%d]", e.Error(),
|
|
r.Path, l, c))
|
|
}
|
|
}
|
|
return strings.Join(msgs, "\n")
|
|
}
|
|
|
|
// Resolver will use a *index.SpecIndex to stitch together a resolved root tree using all the discovered
|
|
// references in the doc.
|
|
type Resolver struct {
|
|
specIndex *SpecIndex
|
|
resolvedRoot *yaml.Node
|
|
resolvingErrors []*ResolvingError
|
|
circularReferences []*CircularReferenceResult
|
|
ignoredPolyReferences []*CircularReferenceResult
|
|
ignoredArrayReferences []*CircularReferenceResult
|
|
referencesVisited int
|
|
indexesVisited int
|
|
journeysTaken int
|
|
relativesSeen int
|
|
IgnorePoly bool
|
|
IgnoreArray bool
|
|
circChecked bool
|
|
}
|
|
|
|
// NewResolver will create a new resolver from a *index.SpecIndex
|
|
func NewResolver(index *SpecIndex) *Resolver {
|
|
if index == nil {
|
|
return nil
|
|
}
|
|
r := &Resolver{
|
|
specIndex: index,
|
|
resolvedRoot: index.GetRootNode(),
|
|
}
|
|
index.resolver = r
|
|
return r
|
|
}
|
|
|
|
// GetIgnoredCircularPolyReferences returns all ignored circular references that are polymorphic
|
|
func (resolver *Resolver) GetIgnoredCircularPolyReferences() []*CircularReferenceResult {
|
|
return resolver.ignoredPolyReferences
|
|
}
|
|
|
|
// GetIgnoredCircularArrayReferences returns all ignored circular references that are arrays
|
|
func (resolver *Resolver) GetIgnoredCircularArrayReferences() []*CircularReferenceResult {
|
|
return resolver.ignoredArrayReferences
|
|
}
|
|
|
|
// GetResolvingErrors returns all errors found during resolving
|
|
func (resolver *Resolver) GetResolvingErrors() []*ResolvingError {
|
|
return resolver.resolvingErrors
|
|
}
|
|
|
|
func (resolver *Resolver) GetCircularReferences() []*CircularReferenceResult {
|
|
return resolver.GetSafeCircularReferences()
|
|
}
|
|
|
|
// GetSafeCircularReferences returns all circular reference errors found.
|
|
func (resolver *Resolver) GetSafeCircularReferences() []*CircularReferenceResult {
|
|
var refs []*CircularReferenceResult
|
|
for _, ref := range resolver.circularReferences {
|
|
if !ref.IsInfiniteLoop {
|
|
refs = append(refs, ref)
|
|
}
|
|
}
|
|
return refs
|
|
}
|
|
|
|
// GetInfiniteCircularReferences returns all circular reference errors found that are infinite / unrecoverable
|
|
func (resolver *Resolver) GetInfiniteCircularReferences() []*CircularReferenceResult {
|
|
var refs []*CircularReferenceResult
|
|
for _, ref := range resolver.circularReferences {
|
|
if ref.IsInfiniteLoop {
|
|
refs = append(refs, ref)
|
|
}
|
|
}
|
|
return refs
|
|
}
|
|
|
|
// GetPolymorphicCircularErrors returns all circular errors that stem from polymorphism
|
|
func (resolver *Resolver) GetPolymorphicCircularErrors() []*CircularReferenceResult {
|
|
var res []*CircularReferenceResult
|
|
for i := range resolver.circularReferences {
|
|
if !resolver.circularReferences[i].IsInfiniteLoop {
|
|
continue
|
|
}
|
|
if !resolver.circularReferences[i].IsPolymorphicResult {
|
|
continue
|
|
}
|
|
res = append(res, resolver.circularReferences[i])
|
|
}
|
|
return res
|
|
}
|
|
|
|
// GetNonPolymorphicCircularErrors returns all circular errors that DO NOT stem from polymorphism
|
|
func (resolver *Resolver) GetNonPolymorphicCircularErrors() []*CircularReferenceResult {
|
|
var res []*CircularReferenceResult
|
|
for i := range resolver.circularReferences {
|
|
if !resolver.circularReferences[i].IsInfiniteLoop {
|
|
continue
|
|
}
|
|
|
|
if !resolver.circularReferences[i].IsPolymorphicResult {
|
|
res = append(res, resolver.circularReferences[i])
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
// IgnorePolymorphicCircularReferences will ignore any circular references that are polymorphic (oneOf, anyOf, allOf)
|
|
// This must be set before any resolving is done.
|
|
func (resolver *Resolver) IgnorePolymorphicCircularReferences() {
|
|
resolver.IgnorePoly = true
|
|
}
|
|
|
|
// IgnoreArrayCircularReferences will ignore any circular references that stem from arrays. This must be set before
|
|
// any resolving is done.
|
|
func (resolver *Resolver) IgnoreArrayCircularReferences() {
|
|
resolver.IgnoreArray = true
|
|
}
|
|
|
|
// GetJourneysTaken returns the number of journeys taken by the resolver
|
|
func (resolver *Resolver) GetJourneysTaken() int {
|
|
return resolver.journeysTaken
|
|
}
|
|
|
|
// GetReferenceVisited returns the number of references visited by the resolver
|
|
func (resolver *Resolver) GetReferenceVisited() int {
|
|
return resolver.referencesVisited
|
|
}
|
|
|
|
// GetIndexesVisited returns the number of indexes visited by the resolver
|
|
func (resolver *Resolver) GetIndexesVisited() int {
|
|
return resolver.indexesVisited
|
|
}
|
|
|
|
// GetRelativesSeen returns the number of siblings (nodes at the same level) seen for each reference found.
|
|
func (resolver *Resolver) GetRelativesSeen() int {
|
|
return resolver.relativesSeen
|
|
}
|
|
|
|
// Resolve will resolve the specification, everything that is not polymorphic and not circular, will be resolved.
|
|
// this data can get big, it results in a massive duplication of data. This is a destructive method and will permanently
|
|
// re-organize the node tree. Make sure you have copied your original tree before running this (if you want to preserve
|
|
// original data)
|
|
func (resolver *Resolver) Resolve() []*ResolvingError {
|
|
visitIndex(resolver, resolver.specIndex)
|
|
|
|
for _, circRef := range resolver.circularReferences {
|
|
// If the circular reference is not required, we can ignore it, as it's a terminable loop rather than an infinite one
|
|
if !circRef.IsInfiniteLoop {
|
|
continue
|
|
}
|
|
|
|
if !resolver.circChecked {
|
|
resolver.resolvingErrors = append(resolver.resolvingErrors, &ResolvingError{
|
|
ErrorRef: fmt.Errorf("infinite circular reference detected: %s", circRef.Start.Definition),
|
|
Node: circRef.ParentNode,
|
|
Path: circRef.GenerateJourneyPath(),
|
|
CircularReference: circRef,
|
|
})
|
|
}
|
|
}
|
|
resolver.specIndex.SetCircularReferences(resolver.circularReferences)
|
|
resolver.specIndex.SetIgnoredArrayCircularReferences(resolver.ignoredArrayReferences)
|
|
resolver.specIndex.SetIgnoredPolymorphicCircularReferences(resolver.ignoredPolyReferences)
|
|
resolver.circChecked = true
|
|
return resolver.resolvingErrors
|
|
}
|
|
|
|
// CheckForCircularReferences Check for circular references, without resolving, a non-destructive run.
|
|
func (resolver *Resolver) CheckForCircularReferences() []*ResolvingError {
|
|
visitIndexWithoutDamagingIt(resolver, resolver.specIndex)
|
|
for _, circRef := range resolver.circularReferences {
|
|
// If the circular reference is not required, we can ignore it, as it's a terminable loop rather than an infinite one
|
|
if !circRef.IsInfiniteLoop {
|
|
continue
|
|
}
|
|
if !resolver.circChecked {
|
|
resolver.resolvingErrors = append(resolver.resolvingErrors, &ResolvingError{
|
|
ErrorRef: fmt.Errorf("infinite circular reference detected: %s", circRef.Start.Name),
|
|
Node: circRef.ParentNode,
|
|
Path: circRef.GenerateJourneyPath(),
|
|
CircularReference: circRef,
|
|
})
|
|
}
|
|
}
|
|
// update our index with any circular refs we found.
|
|
resolver.specIndex.SetCircularReferences(resolver.circularReferences)
|
|
resolver.specIndex.SetIgnoredArrayCircularReferences(resolver.ignoredArrayReferences)
|
|
resolver.specIndex.SetIgnoredPolymorphicCircularReferences(resolver.ignoredPolyReferences)
|
|
resolver.circChecked = true
|
|
return resolver.resolvingErrors
|
|
}
|
|
|
|
func visitIndexWithoutDamagingIt(res *Resolver, idx *SpecIndex) {
|
|
mapped := idx.GetMappedReferencesSequenced()
|
|
mappedIndex := idx.GetMappedReferences()
|
|
res.indexesVisited++
|
|
for _, ref := range mapped {
|
|
seenReferences := make(map[string]bool)
|
|
var journey []*Reference
|
|
res.journeysTaken++
|
|
res.VisitReference(ref.Reference, seenReferences, journey, false)
|
|
}
|
|
schemas := idx.GetAllComponentSchemas()
|
|
for s, schemaRef := range schemas {
|
|
if mappedIndex[s] == nil {
|
|
seenReferences := make(map[string]bool)
|
|
var journey []*Reference
|
|
res.journeysTaken++
|
|
res.VisitReference(schemaRef, seenReferences, journey, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
type refMap struct {
|
|
ref *Reference
|
|
nodes []*yaml.Node
|
|
}
|
|
|
|
func visitIndex(res *Resolver, idx *SpecIndex) {
|
|
mapped := idx.GetMappedReferencesSequenced()
|
|
mappedIndex := idx.GetMappedReferences()
|
|
res.indexesVisited++
|
|
|
|
var refs []refMap
|
|
for _, ref := range mapped {
|
|
seenReferences := make(map[string]bool)
|
|
var journey []*Reference
|
|
res.journeysTaken++
|
|
if ref != nil && ref.Reference != nil {
|
|
n := res.VisitReference(ref.Reference, seenReferences, journey, true)
|
|
if !ref.Reference.Circular {
|
|
// make a note of the reference and map the original ref after we're done
|
|
if ok, _, _ := utils.IsNodeRefValue(ref.OriginalReference.Node); ok {
|
|
refs = append(refs, refMap{
|
|
ref: ref.OriginalReference,
|
|
nodes: n,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
idx.pendingResolve = refs
|
|
|
|
schemas := idx.GetAllComponentSchemas()
|
|
for s, schemaRef := range schemas {
|
|
if mappedIndex[s] == nil {
|
|
seenReferences := make(map[string]bool)
|
|
var journey []*Reference
|
|
res.journeysTaken++
|
|
schemaRef.Node.Content = res.VisitReference(schemaRef, seenReferences, journey, true)
|
|
}
|
|
}
|
|
|
|
schemas = idx.GetAllSecuritySchemes()
|
|
for s, schemaRef := range schemas {
|
|
if mappedIndex[s] == nil {
|
|
seenReferences := make(map[string]bool)
|
|
var journey []*Reference
|
|
res.journeysTaken++
|
|
schemaRef.Node.Content = res.VisitReference(schemaRef, seenReferences, journey, true)
|
|
}
|
|
}
|
|
|
|
// map everything
|
|
for _, sequenced := range idx.GetAllSequencedReferences() {
|
|
locatedDef := mappedIndex[sequenced.Definition]
|
|
if locatedDef != nil {
|
|
if !locatedDef.Circular && locatedDef.Seen {
|
|
sequenced.Node.Content = locatedDef.Node.Content
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// VisitReference will visit a reference as part of a journey and will return resolved nodes.
|
|
func (resolver *Resolver) VisitReference(ref *Reference, seen map[string]bool, journey []*Reference, resolve bool) []*yaml.Node {
|
|
resolver.referencesVisited++
|
|
if resolve && ref.Seen {
|
|
if ref.Resolved {
|
|
return ref.Node.Content
|
|
}
|
|
}
|
|
if !resolve && ref.Seen {
|
|
return ref.Node.Content
|
|
}
|
|
|
|
journey = append(journey, ref)
|
|
seenRelatives := make(map[int]bool)
|
|
relatives := resolver.extractRelatives(ref, ref.Node, nil, seen, journey, seenRelatives, resolve, 0)
|
|
|
|
seen = make(map[string]bool)
|
|
|
|
seen[ref.Definition] = true
|
|
for _, r := range relatives {
|
|
// check if we have seen this on the journey before, if so! it's circular
|
|
skip := false
|
|
for i, j := range journey {
|
|
if j.FullDefinition == r.FullDefinition {
|
|
|
|
var foundDup *Reference
|
|
foundRef, _ := resolver.specIndex.SearchIndexForReferenceByReference(r)
|
|
if foundRef != nil {
|
|
foundDup = foundRef
|
|
}
|
|
|
|
var circRef *CircularReferenceResult
|
|
if !foundDup.Circular {
|
|
loop := append(journey, foundDup)
|
|
|
|
visitedDefinitions := make(map[string]bool)
|
|
isInfiniteLoop, _ := resolver.isInfiniteCircularDependency(foundDup,
|
|
visitedDefinitions, nil)
|
|
|
|
isArray := false
|
|
if r.ParentNodeSchemaType == "array" || slices.Contains(r.ParentNodeTypes, "array") {
|
|
isArray = true
|
|
}
|
|
circRef = &CircularReferenceResult{
|
|
ParentNode: foundDup.ParentNode,
|
|
Journey: loop,
|
|
Start: foundDup,
|
|
LoopIndex: i,
|
|
LoopPoint: foundDup,
|
|
IsArrayResult: isArray,
|
|
IsInfiniteLoop: isInfiniteLoop,
|
|
}
|
|
|
|
if resolver.IgnorePoly && !isArray {
|
|
resolver.ignoredPolyReferences = append(resolver.ignoredPolyReferences, circRef)
|
|
} else if resolver.IgnoreArray && isArray {
|
|
resolver.ignoredArrayReferences = append(resolver.ignoredArrayReferences, circRef)
|
|
} else {
|
|
if !resolver.circChecked {
|
|
resolver.circularReferences = append(resolver.circularReferences, circRef)
|
|
}
|
|
}
|
|
r.Seen = true
|
|
r.Circular = true
|
|
foundDup.Seen = true
|
|
foundDup.Circular = true
|
|
}
|
|
skip = true
|
|
}
|
|
}
|
|
|
|
if !skip {
|
|
var original *Reference
|
|
foundRef, _ := resolver.specIndex.SearchIndexForReferenceByReference(r)
|
|
if foundRef != nil {
|
|
original = foundRef
|
|
}
|
|
resolved := resolver.VisitReference(original, seen, journey, resolve)
|
|
if resolve && !original.Circular {
|
|
ref.Resolved = true
|
|
r.Resolved = true
|
|
r.Node.Content = resolved // this is where we perform the actual resolving.
|
|
}
|
|
r.Seen = true
|
|
ref.Seen = true
|
|
}
|
|
}
|
|
|
|
ref.Seen = true
|
|
|
|
return ref.Node.Content
|
|
}
|
|
|
|
func (resolver *Resolver) isInfiniteCircularDependency(ref *Reference, visitedDefinitions map[string]bool,
|
|
initialRef *Reference,
|
|
) (bool, map[string]bool) {
|
|
if ref == nil {
|
|
return false, visitedDefinitions
|
|
}
|
|
for refDefinition := range ref.RequiredRefProperties {
|
|
r, _ := resolver.specIndex.SearchIndexForReference(refDefinition)
|
|
if initialRef != nil && initialRef.FullDefinition == r.FullDefinition {
|
|
return true, visitedDefinitions
|
|
}
|
|
if len(visitedDefinitions) > 0 && ref.FullDefinition == r.FullDefinition {
|
|
return true, visitedDefinitions
|
|
}
|
|
|
|
if visitedDefinitions[r.FullDefinition] {
|
|
continue
|
|
}
|
|
|
|
visitedDefinitions[r.FullDefinition] = true
|
|
|
|
ir := initialRef
|
|
if ir == nil {
|
|
ir = ref
|
|
}
|
|
|
|
var isChildICD bool
|
|
|
|
isChildICD, visitedDefinitions = resolver.isInfiniteCircularDependency(r, visitedDefinitions, ir)
|
|
if isChildICD {
|
|
return true, visitedDefinitions
|
|
}
|
|
}
|
|
|
|
return false, visitedDefinitions
|
|
}
|
|
|
|
func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.Node,
|
|
foundRelatives map[string]bool,
|
|
journey []*Reference, seen map[int]bool, resolve bool, depth int,
|
|
) []*Reference {
|
|
if len(journey) > 100 {
|
|
return nil
|
|
}
|
|
|
|
// this is a safety check to prevent a stack overflow.
|
|
if depth > 500 {
|
|
def := "unknown"
|
|
if ref != nil {
|
|
def = ref.FullDefinition
|
|
}
|
|
if resolver.specIndex != nil && resolver.specIndex.logger != nil {
|
|
resolver.specIndex.logger.Warn("libopenapi resolver: relative depth exceeded 100 levels, "+
|
|
"check for circular references - resolving may be incomplete",
|
|
"reference", def)
|
|
}
|
|
|
|
loop := append(journey, ref)
|
|
circRef := &CircularReferenceResult{
|
|
Journey: loop,
|
|
Start: ref,
|
|
LoopIndex: depth,
|
|
LoopPoint: ref,
|
|
IsInfiniteLoop: true,
|
|
}
|
|
if !resolver.circChecked {
|
|
resolver.circularReferences = append(resolver.circularReferences, circRef)
|
|
ref.Circular = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var found []*Reference
|
|
|
|
if len(node.Content) > 0 {
|
|
skip := false
|
|
for i, n := range node.Content {
|
|
if skip {
|
|
skip = false
|
|
continue
|
|
}
|
|
if utils.IsNodeMap(n) || utils.IsNodeArray(n) {
|
|
depth++
|
|
|
|
var foundRef *Reference
|
|
foundRef, _ = resolver.specIndex.SearchIndexForReferenceByReference(ref)
|
|
if foundRef != nil && !foundRef.Circular {
|
|
found = append(found, resolver.extractRelatives(foundRef, n, node, foundRelatives, journey, seen, resolve, depth)...)
|
|
depth--
|
|
}
|
|
if foundRef == nil {
|
|
found = append(found, resolver.extractRelatives(ref, n, node, foundRelatives, journey, seen, resolve, depth)...)
|
|
depth--
|
|
}
|
|
|
|
}
|
|
|
|
if i%2 == 0 && n.Value == "$ref" {
|
|
|
|
if !utils.IsNodeStringValue(node.Content[i+1]) {
|
|
continue
|
|
}
|
|
|
|
value := node.Content[i+1].Value
|
|
value = strings.ReplaceAll(value, "\\\\", "\\")
|
|
var locatedRef *Reference
|
|
var fullDef string
|
|
var definition string
|
|
|
|
// explode value
|
|
exp := strings.Split(value, "#/")
|
|
if len(exp) == 2 {
|
|
definition = fmt.Sprintf("#/%s", exp[1])
|
|
if exp[0] != "" {
|
|
if !strings.HasPrefix(exp[0], "http") {
|
|
|
|
if strings.HasPrefix(ref.FullDefinition, "http") {
|
|
|
|
// split the http URI into parts
|
|
httpExp := strings.Split(ref.FullDefinition, "#/")
|
|
|
|
u, _ := url.Parse(httpExp[0])
|
|
abs, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(u.Path), exp[0], string(filepath.Separator)))
|
|
u.Path = utils.ReplaceWindowsDriveWithLinuxPath(abs)
|
|
u.Fragment = ""
|
|
fullDef = fmt.Sprintf("%s#/%s", u.String(), exp[1])
|
|
|
|
} else {
|
|
|
|
// split the referring ref full def into parts
|
|
fileDef := strings.Split(ref.FullDefinition, "#/")
|
|
|
|
// extract the location of the ref and build a full def path.
|
|
abs, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(fileDef[0]), exp[0], string(filepath.Separator)))
|
|
//abs = utils.ReplaceWindowsDriveWithLinuxPath(abs)
|
|
fullDef = fmt.Sprintf("%s#/%s", abs, exp[1])
|
|
|
|
}
|
|
|
|
}
|
|
} else {
|
|
// local component, full def is based on passed in ref
|
|
if strings.HasPrefix(ref.FullDefinition, "http") {
|
|
|
|
// split the http URI into parts
|
|
httpExp := strings.Split(ref.FullDefinition, "#/")
|
|
|
|
// parse a URL from the full def
|
|
u, _ := url.Parse(httpExp[0])
|
|
|
|
// extract the location of the ref and build a full def path.
|
|
fullDef = fmt.Sprintf("%s#/%s", u.String(), exp[1])
|
|
|
|
} else {
|
|
// split the full def into parts
|
|
fileDef := strings.Split(ref.FullDefinition, "#/")
|
|
fullDef = fmt.Sprintf("%s#/%s", fileDef[0], exp[1])
|
|
}
|
|
}
|
|
} else {
|
|
|
|
definition = value
|
|
|
|
// if the reference is a http link
|
|
if strings.HasPrefix(value, "http") {
|
|
fullDef = value
|
|
} else {
|
|
|
|
// split the full def into parts
|
|
fileDef := strings.Split(ref.FullDefinition, "#/")
|
|
|
|
// is the file def a http link?
|
|
if strings.HasPrefix(fileDef[0], "http") {
|
|
u, _ := url.Parse(fileDef[0])
|
|
path, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(u.Path), exp[0], string(filepath.Separator)))
|
|
u.Path = utils.ReplaceWindowsDriveWithLinuxPath(path)
|
|
fullDef = u.String()
|
|
|
|
} else {
|
|
fullDef, _ = filepath.Abs(utils.CheckPathOverlap(filepath.Dir(fileDef[0]), exp[0], string(filepath.Separator)))
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
searchRef := &Reference{
|
|
Definition: definition,
|
|
FullDefinition: fullDef,
|
|
RemoteLocation: ref.RemoteLocation,
|
|
IsRemote: true,
|
|
}
|
|
|
|
locatedRef, _ = resolver.specIndex.SearchIndexForReferenceByReference(searchRef)
|
|
|
|
if locatedRef == nil {
|
|
_, path := utils.ConvertComponentIdIntoFriendlyPathSearch(value)
|
|
err := &ResolvingError{
|
|
ErrorRef: fmt.Errorf("cannot resolve reference `%s`, it's missing", value),
|
|
Node: n,
|
|
Path: path,
|
|
}
|
|
resolver.resolvingErrors = append(resolver.resolvingErrors, err)
|
|
continue
|
|
}
|
|
|
|
if resolve {
|
|
// if this is a reference also, we want to resolve it.
|
|
if ok, _, _ := utils.IsNodeRefValue(ref.Node); ok {
|
|
ref.Node.Content = locatedRef.Node.Content
|
|
ref.Resolved = true
|
|
}
|
|
}
|
|
|
|
schemaType := ""
|
|
if parent != nil {
|
|
_, arrayTypevn := utils.FindKeyNodeTop("type", parent.Content)
|
|
if arrayTypevn != nil {
|
|
if arrayTypevn.Value == "array" {
|
|
schemaType = "array"
|
|
}
|
|
}
|
|
}
|
|
if ref.ParentNodeSchemaType != "" {
|
|
locatedRef.ParentNodeTypes = append(locatedRef.ParentNodeTypes, ref.ParentNodeSchemaType)
|
|
}
|
|
locatedRef.ParentNodeSchemaType = schemaType
|
|
found = append(found, locatedRef)
|
|
foundRelatives[value] = true
|
|
}
|
|
|
|
if i%2 == 0 && n.Value != "$ref" && n.Value != "" {
|
|
if n.Value == "allOf" ||
|
|
n.Value == "oneOf" ||
|
|
n.Value == "anyOf" {
|
|
|
|
// if this is a polymorphic link, we want to follow it and see if it becomes circular
|
|
if i+1 <= len(node.Content) && utils.IsNodeMap(node.Content[i+1]) { // check for nested items
|
|
// check if items is present, to indicate an array
|
|
if k, v := utils.FindKeyNodeTop("items", node.Content[i+1].Content); v != nil {
|
|
if utils.IsNodeMap(v) {
|
|
if d, _, l := utils.IsNodeRefValue(v); d {
|
|
|
|
// create full definition lookup based on ref.
|
|
def := resolver.buildDefPath(ref, l)
|
|
|
|
mappedRefs, _ := resolver.specIndex.SearchIndexForReference(def)
|
|
if mappedRefs != nil && !mappedRefs.Circular {
|
|
circ := false
|
|
for f := range journey {
|
|
if journey[f].FullDefinition == mappedRefs.FullDefinition {
|
|
circ = true
|
|
break
|
|
}
|
|
}
|
|
if !circ {
|
|
resolver.VisitReference(mappedRefs, foundRelatives, journey, resolve)
|
|
} else {
|
|
loop := append(journey, mappedRefs)
|
|
circRef := &CircularReferenceResult{
|
|
ParentNode: k,
|
|
Journey: loop,
|
|
Start: mappedRefs,
|
|
LoopIndex: i,
|
|
LoopPoint: mappedRefs,
|
|
PolymorphicType: n.Value,
|
|
IsPolymorphicResult: true,
|
|
}
|
|
|
|
mappedRefs.Seen = true
|
|
mappedRefs.Circular = true
|
|
if resolver.IgnorePoly {
|
|
resolver.ignoredPolyReferences = append(resolver.ignoredPolyReferences, circRef)
|
|
} else {
|
|
if !resolver.circChecked {
|
|
resolver.circularReferences = append(resolver.circularReferences, circRef)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// no items discovered, continue on and investigate anyway.
|
|
v := node.Content[i+1]
|
|
if utils.IsNodeMap(v) {
|
|
if d, _, l := utils.IsNodeRefValue(v); d {
|
|
|
|
// create full definition lookup based on ref.
|
|
def := resolver.buildDefPath(ref, l)
|
|
|
|
mappedRefs, _ := resolver.specIndex.SearchIndexForReference(def)
|
|
if mappedRefs != nil && !mappedRefs.Circular {
|
|
circ := false
|
|
for f := range journey {
|
|
if journey[f].FullDefinition == mappedRefs.FullDefinition {
|
|
circ = true
|
|
break
|
|
}
|
|
}
|
|
if !circ {
|
|
resolver.VisitReference(mappedRefs, foundRelatives, journey, resolve)
|
|
} else {
|
|
loop := append(journey, mappedRefs)
|
|
circRef := &CircularReferenceResult{
|
|
ParentNode: node.Content[i],
|
|
Journey: loop,
|
|
Start: mappedRefs,
|
|
LoopIndex: i,
|
|
LoopPoint: mappedRefs,
|
|
PolymorphicType: n.Value,
|
|
IsPolymorphicResult: true,
|
|
}
|
|
|
|
mappedRefs.Seen = true
|
|
mappedRefs.Circular = true
|
|
if resolver.IgnorePoly {
|
|
resolver.ignoredPolyReferences = append(resolver.ignoredPolyReferences, circRef)
|
|
} else {
|
|
if !resolver.circChecked {
|
|
resolver.circularReferences = append(resolver.circularReferences, circRef)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// for array based polymorphic items
|
|
if i+1 <= len(node.Content) && utils.IsNodeArray(node.Content[i+1]) { // check for nested items
|
|
for q := range node.Content[i+1].Content {
|
|
v := node.Content[i+1].Content[q]
|
|
if utils.IsNodeMap(v) {
|
|
if d, _, l := utils.IsNodeRefValue(v); d {
|
|
def := resolver.buildDefPath(ref, l)
|
|
mappedRefs, _ := resolver.specIndex.SearchIndexForReference(def)
|
|
if mappedRefs != nil && !mappedRefs.Circular {
|
|
circ := false
|
|
for f := range journey {
|
|
if journey[f].FullDefinition == mappedRefs.FullDefinition {
|
|
circ = true
|
|
break
|
|
}
|
|
}
|
|
if !circ {
|
|
resolver.VisitReference(mappedRefs, foundRelatives, journey, resolve)
|
|
} else {
|
|
loop := append(journey, mappedRefs)
|
|
|
|
circRef := &CircularReferenceResult{
|
|
ParentNode: node.Content[i],
|
|
Journey: loop,
|
|
Start: mappedRefs,
|
|
LoopIndex: i,
|
|
LoopPoint: mappedRefs,
|
|
PolymorphicType: n.Value,
|
|
IsPolymorphicResult: true,
|
|
}
|
|
|
|
mappedRefs.Seen = true
|
|
mappedRefs.Circular = true
|
|
if resolver.IgnorePoly {
|
|
resolver.ignoredPolyReferences = append(resolver.ignoredPolyReferences, circRef)
|
|
} else {
|
|
if !resolver.circChecked {
|
|
resolver.circularReferences = append(resolver.circularReferences, circRef)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
depth++
|
|
found = append(found, resolver.extractRelatives(ref, v, n,
|
|
foundRelatives, journey, seen, resolve, depth)...)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
skip = true
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
resolver.relativesSeen += len(found)
|
|
return found
|
|
}
|
|
|
|
func (resolver *Resolver) buildDefPath(ref *Reference, l string) string {
|
|
def := ""
|
|
exp := strings.Split(l, "#/")
|
|
if len(exp) == 2 {
|
|
if exp[0] != "" {
|
|
if !strings.HasPrefix(exp[0], "http") {
|
|
if !filepath.IsAbs(exp[0]) {
|
|
if strings.HasPrefix(ref.FullDefinition, "http") {
|
|
|
|
u, _ := url.Parse(ref.FullDefinition)
|
|
p, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(u.Path), exp[0], string(filepath.Separator)))
|
|
u.Path = utils.ReplaceWindowsDriveWithLinuxPath(p)
|
|
def = fmt.Sprintf("%s#/%s", u.String(), exp[1])
|
|
|
|
} else {
|
|
z := strings.Split(ref.FullDefinition, "#/")
|
|
if len(z) == 2 {
|
|
if len(z[0]) > 0 {
|
|
abs, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(z[0]),
|
|
exp[0], string(filepath.Separator)))
|
|
def = fmt.Sprintf("%s#/%s", abs, exp[1])
|
|
} else {
|
|
abs, _ := filepath.Abs(exp[0])
|
|
def = fmt.Sprintf("%s#/%s", abs, exp[1])
|
|
}
|
|
} else {
|
|
abs, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(ref.FullDefinition),
|
|
exp[0], string(filepath.Separator)))
|
|
def = fmt.Sprintf("%s#/%s", abs, exp[1])
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if len(exp[1]) > 0 {
|
|
def = l
|
|
} else {
|
|
def = exp[0]
|
|
}
|
|
}
|
|
} else {
|
|
if strings.HasPrefix(ref.FullDefinition, "http") {
|
|
u, _ := url.Parse(ref.FullDefinition)
|
|
u.Fragment = ""
|
|
def = fmt.Sprintf("%s#/%s", u.String(), exp[1])
|
|
|
|
} else {
|
|
if strings.HasPrefix(ref.FullDefinition, "#/") {
|
|
def = fmt.Sprintf("#/%s", exp[1])
|
|
} else {
|
|
fdexp := strings.Split(ref.FullDefinition, "#/")
|
|
def = fmt.Sprintf("%s#/%s", fdexp[0], exp[1])
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if strings.HasPrefix(l, "http") {
|
|
def = l
|
|
} else {
|
|
|
|
// check if were dealing with a remote file
|
|
if strings.HasPrefix(ref.FullDefinition, "http") {
|
|
|
|
// split the url.
|
|
u, _ := url.Parse(ref.FullDefinition)
|
|
abs, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(u.Path), l, string(filepath.Separator)))
|
|
u.Path = utils.ReplaceWindowsDriveWithLinuxPath(abs)
|
|
u.Fragment = ""
|
|
def = u.String()
|
|
} else {
|
|
lookupRef := strings.Split(ref.FullDefinition, "#/")
|
|
abs, _ := filepath.Abs(utils.CheckPathOverlap(filepath.Dir(lookupRef[0]), l, string(filepath.Separator)))
|
|
def = abs
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return def
|
|
}
|
|
|
|
func (resolver *Resolver) ResolvePendingNodes() {
|
|
// map everything afterwards
|
|
for _, r := range resolver.specIndex.pendingResolve {
|
|
// r.Node.Content = refs[r].nodes
|
|
r.ref.Node.Content = r.nodes
|
|
}
|
|
}
|