Skip to content

Commit 80d86bd

Browse files
committed
Extract components for bundling
1 parent 62fb3ae commit 80d86bd

File tree

1 file changed

+77
-56
lines changed

1 file changed

+77
-56
lines changed

bundler/bundler.go

Lines changed: 77 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package bundler
66

77
import (
88
"errors"
9-
"strings"
9+
"fmt"
1010

1111
"github.com/pb33f/libopenapi"
1212
"github.com/pb33f/libopenapi/datamodel"
@@ -20,7 +20,6 @@ var ErrInvalidModel = errors.New("invalid model")
2020
type RefHandling string
2121

2222
const (
23-
RefHandlingIgnore RefHandling = "ignore"
2423
RefHandlingInline RefHandling = "inline"
2524
RefHandlingCompose RefHandling = "compose"
2625
)
@@ -68,74 +67,96 @@ func BundleBytes(bytes []byte, configuration *datamodel.DocumentConfiguration, o
6867
//
6968
// Circular references will not be resolved and will be skipped.
7069
func BundleDocument(model *v3.Document) ([]byte, error) {
71-
return bundle(model, BundleOptions{RelativeRefHandling: RefHandlingIgnore})
70+
return bundle(model, BundleOptions{RelativeRefHandling: RefHandlingInline})
7271
}
7372

74-
func bundle(model *v3.Document, opts BundleOptions) ([]byte, error) {
73+
func bundle(model *v3.Document, opts BundleOptions) (_ []byte, err error) {
7574
rolodex := model.Rolodex
7675

77-
indexes := rolodex.GetIndexes()
78-
for _, idx := range indexes {
79-
handleRefs(idx, opts, false)
80-
}
81-
82-
handleRefs(rolodex.GetRootIndex(), opts, true)
83-
return model.Render()
84-
}
85-
86-
func handleRefs(idx *index.SpecIndex, opts BundleOptions, root bool) {
87-
inline := opts.RelativeRefHandling == RefHandlingInline
88-
76+
idx := rolodex.GetRootIndex()
8977
mappedReferences := idx.GetMappedReferences()
9078
sequencedReferences := idx.GetRawReferencesSequenced()
91-
for _, sequenced := range sequencedReferences {
92-
// if we're in the root document, don't bundle anything.
93-
refExp := strings.Split(sequenced.FullDefinition, "#/")
94-
if len(refExp) == 2 {
95-
if refExp[0] == sequenced.Index.GetSpecAbsolutePath() || refExp[0] == "" {
96-
if root && !inline {
97-
idx.GetLogger().Debug("[bundler] skipping local root reference",
98-
"ref", sequenced.Definition)
99-
continue
100-
}
101-
}
102-
}
10379

80+
for _, sequenced := range sequencedReferences {
10481
mappedReference := mappedReferences[sequenced.FullDefinition]
10582
if mappedReference == nil {
106-
if idx.GetLogger() != nil {
107-
idx.GetLogger().Warn("[bundler] skipping unresolved reference",
108-
"ref", sequenced.FullDefinition)
109-
}
83+
return nil, fmt.Errorf("no mapped reference found for: %s", sequenced.FullDefinition)
84+
}
85+
86+
if mappedReference.DefinitionFile() == idx.GetSpecAbsolutePath() {
87+
// Don't bundle anything that's in the main file.
11088
continue
11189
}
11290

113-
if mappedReference.Circular {
114-
if idx.GetLogger() != nil {
115-
idx.GetLogger().Warn("[bundler] skipping circular reference",
116-
"ref", sequenced.FullDefinition)
91+
switch opts.RelativeRefHandling {
92+
case RefHandlingInline:
93+
// Just deal with simple inlining.
94+
sequenced.Node.Content = mappedReference.Node.Content
95+
case RefHandlingCompose:
96+
// Recursively collect all reference targets to be bundled into the root
97+
// file.
98+
bundledComponents := make(map[string]*index.ReferenceNode)
99+
if err := bundleRefTarget(sequenced, mappedReference, bundledComponents, opts); err != nil {
100+
return nil, err
117101
}
118-
continue
119102
}
103+
}
120104

121-
// NOTE: here we are taking a $ref in a document and we replace its
122-
// content from the referenced document.
123-
// To change this behavior, we'd have to take the local part of the
124-
// FullDefinition, create a matching entry in the root's components
125-
// section and replace the content of the node with a reference to that.
126-
//
127-
// In OpenAPI 3.1 a reference may point to any other JSONSchema file. We
128-
// have to deal with that case and figure out what to name such a
129-
// reference. Alternatively, we may skip that and do the regular
130-
// behavior.
131-
//
132-
// No need to look up the type of a component, because that should be
133-
// evident from the reference itself.
134-
//
135-
// Keep a list of all added references so you can deduplicate.
136-
//
137-
// - Use sequenced.Node.Content to determine the location of the reference
138-
// target in the root document.
139-
sequenced.Node.Content = mappedReference.Node.Content
105+
return model.Render()
106+
}
107+
108+
func bundleRefTarget(ref, mappedRef *index.ReferenceNode, bundledComponents map[string]*index.ReferenceNode, opts BundleOptions) error {
109+
idx := ref.Index
110+
if mappedRef == nil {
111+
if idx.GetLogger() != nil {
112+
idx.GetLogger().Warn("[bundler] skipping unresolved reference",
113+
"ref", ref.FullDefinition)
114+
}
115+
return nil
116+
}
117+
118+
if mappedRef.Circular {
119+
if idx.GetLogger() != nil {
120+
idx.GetLogger().Warn("[bundler] skipping circular reference",
121+
"ref", ref.FullDefinition)
122+
}
123+
return nil
124+
}
125+
126+
bundledRef, exists := bundledComponents[mappedRef.Definition]
127+
if exists && bundledRef.FullDefinition != mappedRef.FullDefinition {
128+
// TODO: we don't want to error here
129+
return fmt.Errorf("duplicate component definition: %s", mappedRef.Definition)
130+
} else {
131+
bundledComponents[mappedRef.Definition] = mappedRef
132+
ref.KeyNode.Value = mappedRef.Definition
140133
}
134+
135+
// When composing, we need to update the ref values to point to a local reference. At the
136+
// same time we need to track all components referenced by any children of the target, so
137+
// that we can include them in the final document.
138+
//
139+
// One issue we might face is that the name of a target component in any given target
140+
// document is the same as that of another component in a different target document or
141+
// even the root document.
142+
143+
// Obtain the target's file's index because we should find child references using that.
144+
// Otherwise ExtractRefs will use the ref's index and it's absolute spec path for
145+
// the FullPath of any extracted ref targets.
146+
targetIndex := idx
147+
if targetFile := mappedRef.DefinitionFile(); targetFile != "" {
148+
targetIndex = idx.GetRolodex().GetFileIndex(targetFile)
149+
}
150+
151+
targetMappedReferences := targetIndex.GetMappedReferences()
152+
153+
childRefs := targetIndex.ExtractRefs(mappedRef.Node, mappedRef.ParentNode, make([]string, 0), 0, false, "")
154+
for _, childRef := range childRefs {
155+
childRefTarget := targetMappedReferences[childRef.FullDefinition]
156+
if err := bundleRefTarget(childRef, childRefTarget, bundledComponents, opts); err != nil {
157+
return err
158+
}
159+
}
160+
161+
return nil
141162
}

0 commit comments

Comments
 (0)