@@ -6,7 +6,7 @@ package bundler
6
6
7
7
import (
8
8
"errors"
9
- "strings "
9
+ "fmt "
10
10
11
11
"github.com/pb33f/libopenapi"
12
12
"github.com/pb33f/libopenapi/datamodel"
@@ -20,7 +20,6 @@ var ErrInvalidModel = errors.New("invalid model")
20
20
type RefHandling string
21
21
22
22
const (
23
- RefHandlingIgnore RefHandling = "ignore"
24
23
RefHandlingInline RefHandling = "inline"
25
24
RefHandlingCompose RefHandling = "compose"
26
25
)
@@ -68,74 +67,96 @@ func BundleBytes(bytes []byte, configuration *datamodel.DocumentConfiguration, o
68
67
//
69
68
// Circular references will not be resolved and will be skipped.
70
69
func BundleDocument (model * v3.Document ) ([]byte , error ) {
71
- return bundle (model , BundleOptions {RelativeRefHandling : RefHandlingIgnore })
70
+ return bundle (model , BundleOptions {RelativeRefHandling : RefHandlingInline })
72
71
}
73
72
74
- func bundle (model * v3.Document , opts BundleOptions ) ([]byte , error ) {
73
+ func bundle (model * v3.Document , opts BundleOptions ) (_ []byte , err error ) {
75
74
rolodex := model .Rolodex
76
75
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 ()
89
77
mappedReferences := idx .GetMappedReferences ()
90
78
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
- }
103
79
80
+ for _ , sequenced := range sequencedReferences {
104
81
mappedReference := mappedReferences [sequenced .FullDefinition ]
105
82
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.
110
88
continue
111
89
}
112
90
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
117
101
}
118
- continue
119
102
}
103
+ }
120
104
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
140
133
}
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
141
162
}
0 commit comments