diff --git a/index/extract_refs.go b/index/extract_refs.go index d6ed838..5cbf73c 100644 --- a/index/extract_refs.go +++ b/index/extract_refs.go @@ -563,9 +563,10 @@ func (index *SpecIndex) ExtractComponentsFromRefs(refs []*Reference) []*Referenc found = append(found, located) index.allMappedRefs[located.FullDefinition] = located rm := &ReferenceMapped{ - Reference: located, - Definition: located.Definition, - FullDefinition: located.FullDefinition, + OriginalReference: ref, + Reference: located, + Definition: located.Definition, + FullDefinition: located.FullDefinition, } sequence[refIndex] = rm } diff --git a/index/index_model.go b/index/index_model.go index c5882eb..469826b 100644 --- a/index/index_model.go +++ b/index/index_model.go @@ -44,9 +44,10 @@ type Reference struct { // ReferenceMapped is a helper struct for mapped references put into sequence (we lose the key) type ReferenceMapped struct { - Reference *Reference - Definition string - FullDefinition string + OriginalReference *Reference + Reference *Reference + Definition string + FullDefinition string } // SpecIndexConfig is a configuration struct for the SpecIndex introduced in 0.6.0 that provides an expandable diff --git a/index/resolver.go b/index/resolver.go index 21e8b42..2ab9e96 100644 --- a/index/resolver.go +++ b/index/resolver.go @@ -258,7 +258,11 @@ func visitIndex(res *Resolver, idx *SpecIndex) { var journey []*Reference res.journeysTaken++ if ref != nil && ref.Reference != nil { - ref.Reference.Node.Content = res.VisitReference(ref.Reference, seenReferences, journey, true) + n := res.VisitReference(ref.Reference, seenReferences, journey, true) + ref.Reference.Node.Content = n + if !ref.Reference.Circular { + ref.OriginalReference.Node.Content = n + } } } @@ -548,7 +552,7 @@ func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.No if resolve { // if this is a reference also, we want to resolve it. if ok, _, _ := utils.IsNodeRefValue(ref.Node); ok { - ref.Node = locatedRef.Node + ref.Node.Content = locatedRef.Node.Content ref.Resolved = true } } diff --git a/index/resolver_test.go b/index/resolver_test.go index 31e0904..472a376 100644 --- a/index/resolver_test.go +++ b/index/resolver_test.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/utils" + "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" "net/http" "net/url" "os" @@ -1018,3 +1019,74 @@ func TestLocateRefEnd_WithResolve(t *testing.T) { isRef, _, _ = utils.IsNodeRefValue(ref.Node) assert.False(t, isRef) } + +func TestResolveDoc_Issue195(t *testing.T) { + + spec := `openapi: 3.0.1 +info: + title: Some Example! +paths: + "/pet/findByStatus": + get: + responses: + default: + content: + application/json: + schema: + "$ref": https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml#/components/schemas/Error` + + var rootNode yaml.Node + _ = yaml.Unmarshal([]byte(spec), &rootNode) + + // create an index config + config := CreateOpenAPIIndexConfig() + + // the rolodex will automatically try and check for circular references, you don't want to do this + // if you're resolving the spec, as the node tree is marked as 'seen' and you won't be able to resolve + // correctly. + config.AvoidCircularReferenceCheck = true + + // new in 0.13+ is the ability to add remote and local file systems to the index + // requires a new part, the rolodex. It holds all the indexes and knows where to find + // every reference across local and remote files. + rolodex := NewRolodex(config) + + // add a new remote file system. + remoteFS, _ := NewRemoteFSWithConfig(config) + + // add the remote file system to the rolodex + rolodex.AddRemoteFS("", remoteFS) + + // set the root node of the rolodex, this is your spec. + rolodex.SetRootNode(&rootNode) + + // index the rolodex + indexingError := rolodex.IndexTheRolodex() + if indexingError != nil { + panic(indexingError) + } + + // resolve the rolodex + rolodex.Resolve() + + // there should be no errors at this point + resolvingErrors := rolodex.GetCaughtErrors() + if resolvingErrors != nil { + panic(resolvingErrors) + } + + // perform some lookups. + var nodes []*yaml.Node + + // pull out schema type + path, _ := yamlpath.NewPath("$.paths./pet/findByStatus.get.responses.default.content['application/json'].schema.type") + nodes, _ = path.Find(&rootNode) + assert.Equal(t, nodes[0].Value, "object") + + // pull out required array + path, _ = yamlpath.NewPath("$.paths./pet/findByStatus.get.responses.default.content['application/json'].schema.required") + nodes, _ = path.Find(&rootNode) + assert.Equal(t, nodes[0].Content[0].Value, "code") + assert.Equal(t, nodes[0].Content[1].Value, "message") + +}