// Copyright 2022 Dave Shanley / Quobix // SPDX-License-Identifier: MIT package index import ( "errors" "fmt" "net/url" "path/filepath" "strings" "github.com/pb33f/libopenapi/utils" "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" && len(node.Content) > i%2+1 { if !utils.IsNodeStringValue(node.Content[i+1]) { continue } // issue #481 cannot look at an array value, the next not is not the value! if utils.IsNodeArray(node) { 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") { fullDef = value } else { 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 } }