Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
felixjung committed Nov 11, 2024
1 parent 6610624 commit 865fdb0
Show file tree
Hide file tree
Showing 17 changed files with 452 additions and 371 deletions.
116 changes: 63 additions & 53 deletions bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package bundler

import (
"errors"
"strings"

"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi/datamodel"
Expand All @@ -20,7 +19,6 @@ var ErrInvalidModel = errors.New("invalid model")
type RefHandling string

const (
RefHandlingIgnore RefHandling = "ignore"
RefHandlingInline RefHandling = "inline"
RefHandlingCompose RefHandling = "compose"
)
Expand Down Expand Up @@ -74,68 +72,80 @@ func BundleDocument(model *v3.Document) ([]byte, error) {
func bundle(model *v3.Document, opts BundleOptions) ([]byte, error) {
rolodex := model.Rolodex

indexes := rolodex.GetIndexes()
for _, idx := range indexes {
handleRefs(idx, opts, false)
// indexes := rolodex.GetIndexes()
// for _, idx := range indexes {
// handleRefs(idx, opts)
// }

idx := rolodex.GetRootIndex()
mappedReferences := idx.GetMappedReferences()
sequencedReferences := idx.GetRawReferencesSequenced()

for _, sequenced := range sequencedReferences {
mappedReference := mappedReferences[sequenced.FullDefinition]
bundleRefTarget(sequenced, mappedReference, opts)
}

handleRefs(rolodex.GetRootIndex(), opts, true)
return model.Render()
}

func handleRefs(idx *index.SpecIndex, opts BundleOptions, root bool) {
inline := opts.RelativeRefHandling == RefHandlingInline
// TODO: use orderedmap as return value
func bundleRefTarget(ref, refTarget *index.ReferenceNode, opts BundleOptions) (map[string]*index.ReferenceNode, error) {
idx := ref.Index
if refTarget == nil {
if idx.GetLogger() != nil {
idx.GetLogger().Warn("[bundler] skipping unresolved reference",
"ref", ref.FullDefinition)
}
return nil, nil
}

mappedReferences := idx.GetMappedReferences()
sequencedReferences := idx.GetRawReferencesSequenced()
for _, sequenced := range sequencedReferences {
// if we're in the root document, don't bundle anything.
refExp := strings.Split(sequenced.FullDefinition, "#/")
if len(refExp) == 2 {
if refExp[0] == sequenced.Index.GetSpecAbsolutePath() || refExp[0] == "" {
if root && !inline {
idx.GetLogger().Debug("[bundler] skipping local root reference",
"ref", sequenced.Definition)
continue
}
}
if refTarget.Circular {
if idx.GetLogger() != nil {
idx.GetLogger().Warn("[bundler] skipping circular reference",
"ref", ref.FullDefinition)
}
return nil, nil
}

mappedReference := mappedReferences[sequenced.FullDefinition]
if mappedReference == nil {
if idx.GetLogger() != nil {
idx.GetLogger().Warn("[bundler] skipping unresolved reference",
"ref", sequenced.FullDefinition)
}
continue
switch opts.RelativeRefHandling {
case RefHandlingInline:
ref.Node.Content = refTarget.Node.Content
case RefHandlingCompose:
// When composing, we need to update the ref values to point to a local reference. At the
// same time we need to track all components referenced by any children of the target, so
// that we can include them in the final document.
//
// One issue we might face is that the name of a target component in any given target
// document is the same as that of another component in a different target document or
// even the root document.

// Obtain the target's file's index because we should find child references using that.
// Otherwise ExtractRefs will use the ref's index and it's absolute spec path for
// the FullPath of any extracted ref targets.
targetIndex := idx
if targetFile := refTarget.DefinitionFile(); targetFile != "" {
targetIndex = idx.GetRolodex().GetFileIndex(targetFile)
}

if mappedReference.Circular {
if idx.GetLogger() != nil {
idx.GetLogger().Warn("[bundler] skipping circular reference",
"ref", sequenced.FullDefinition)
}
continue
targetMappedReferences := targetIndex.GetMappedReferences()

bundledComponents := map[string]*index.ReferenceNode{
target

Check failure on line 134 in bundler/bundler.go

View workflow job for this annotation

GitHub Actions / Build Linux

syntax error: unexpected newline in composite literal; possibly missing comma or }

Check failure on line 134 in bundler/bundler.go

View workflow job for this annotation

GitHub Actions / Build Windows

syntax error: unexpected newline in composite literal; possibly missing comma or }
}

// NOTE: here we are taking a $ref in a document and we replace its
// content from the referenced document.
// To change this behavior, we'd have to take the local part of the
// FullDefinition, create a matching entry in the root's components
// section and replace the content of the node with a reference to that.
//
// In OpenAPI 3.1 a reference may point to any other JSONSchema file. We
// have to deal with that case and figure out what to name such a
// reference. Alternatively, we may skip that and do the regular
// behavior.
//
// No need to look up the type of a component, because that should be
// evident from the reference itself.
//
// Keep a list of all added references so you can deduplicate.
//
// - Use sequenced.Node.Content to determine the location of the reference
// target in the root document.
sequenced.Node.Content = mappedReference.Node.Content
childRefs := targetIndex.ExtractRefs(refTarget.Node, refTarget.ParentNode, make([]string, 0), 0, false, "")

for _, childRef := range childRefs {
childRefTarget := targetMappedReferences[childRef.FullDefinition]
childComponents, err := bundleRefTarget(childRef, childRefTarget, opts)
if err != nil {
return nil, err
}
}
}


// TODO:
return nil, nil
}
27 changes: 24 additions & 3 deletions bundler/bundler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"strings"
"testing"

"github.com/davecgh/go-spew/spew"
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/utils"
Expand Down Expand Up @@ -107,7 +108,7 @@ func TestBundleDocument_Circular(t *testing.T) {
assert.Len(t, logEntries, 0)
}

func TestBundleDocument_MinimalRemoteRefsBundledLocally(t *testing.T) {
func TestBundleDocument_MinimalRemoteRefsBundledLocallyComposed(t *testing.T) {
specBytes, err := os.ReadFile("../test_specs/minimal_remote_refs/openapi.yaml")
require.NoError(t, err)

Expand All @@ -116,13 +117,33 @@ func TestBundleDocument_MinimalRemoteRefsBundledLocally(t *testing.T) {
config := &datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: false,
BundleInlineRefs: false,
BasePath: "../test_specs/minimal_remote_refs",
BaseURL: nil,
}
require.NoError(t, err)

bytes, e := BundleBytes(specBytes, config, defaultBundleOpts)
bytes, e := BundleBytes(specBytes, config, BundleOptions{RelativeRefHandling: RefHandlingCompose})
spew.Dump(string(bytes))
assert.NoError(t, e)
assert.Contains(t, string(bytes), "Name of the account", "should contain all reference targets")
}

func TestBundleDocument_MinimalRemoteRefsBundledLocallyInline(t *testing.T) {
specBytes, err := os.ReadFile("../test_specs/minimal_remote_refs/openapi.yaml")
require.NoError(t, err)

require.NoError(t, err)

config := &datamodel.DocumentConfiguration{
AllowFileReferences: true,
AllowRemoteReferences: false,
BasePath: "../test_specs/minimal_remote_refs",
BaseURL: nil,
}
require.NoError(t, err)

bytes, e := BundleBytes(specBytes, config, BundleOptions{RelativeRefHandling: RefHandlingInline})
spew.Dump(string(bytes))
assert.NoError(t, e)
assert.Contains(t, string(bytes), "Name of the account", "should contain all reference targets")
}
Expand Down
6 changes: 3 additions & 3 deletions datamodel/low/extraction_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ func HashExtensions(ext *orderedmap.Map[KeyReference[string], ValueReference[*ya
}

// helper function to generate a list of all the things an index should be searched for.
func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.Reference {
return []func() map[string]*index.Reference{
func generateIndexCollection(idx *index.SpecIndex) []func() map[string]*index.ReferenceNode {
return []func() map[string]*index.ReferenceNode{
idx.GetAllComponentSchemas,
idx.GetMappedReferences,
idx.GetAllExternalDocuments,
Expand All @@ -83,7 +83,7 @@ func LocateRefNodeWithContext(ctx context.Context, root *yaml.Node, idx *index.S
// run through everything and return as soon as we find a match.
// this operates as fast as possible as ever
collections := generateIndexCollection(idx)
var found map[string]*index.Reference
var found map[string]*index.ReferenceNode
for _, collection := range collections {
found = collection()
if found != nil && found[rv] != nil {
Expand Down
2 changes: 1 addition & 1 deletion datamodel/low/extraction_functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1929,7 +1929,7 @@ func TestLocateRefNode_DoARealLookup(t *testing.T) {

// fake cache to a lookup for a file that does not exist will work.
fakeCache := new(sync.Map)
fakeCache.Store(lookup, &index.Reference{Node: &no, Index: idx})
fakeCache.Store(lookup, &index.ReferenceNode{Node: &no, Index: idx})
idx.SetCache(fakeCache)

ctx := context.WithValue(context.Background(), index.CurrentPathKey, lookup)
Expand Down
9 changes: 5 additions & 4 deletions index/circular_reference_result.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package index

import (
"gopkg.in/yaml.v3"
"strings"

"gopkg.in/yaml.v3"
)

// CircularReferenceResult contains a circular reference found when traversing the graph.
type CircularReferenceResult struct {
Journey []*Reference
Journey []*ReferenceNode
ParentNode *yaml.Node
Start *Reference
Start *ReferenceNode
LoopIndex int
LoopPoint *Reference
LoopPoint *ReferenceNode
IsArrayResult bool // if this result comes from an array loop.
PolymorphicType string // which type of polymorphic loop is this? (oneOf, anyOf, allOf)
IsPolymorphicResult bool // if this result comes from a polymorphic loop.
Expand Down
5 changes: 3 additions & 2 deletions index/circular_reference_result_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
package index

import (
"github.com/stretchr/testify/assert"
"testing"

"github.com/stretchr/testify/assert"
)

func TestCircularReferenceResult_GenerateJourneyPath(t *testing.T) {

refs := []*Reference{
refs := []*ReferenceNode{
{Name: "chicken"},
{Name: "nuggets"},
{Name: "chicken"},
Expand Down
Loading

0 comments on commit 865fdb0

Please sign in to comment.